test_vm_settings.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. #!/usr/bin/python3
  2. #
  3. # The Qubes OS Project, https://www.qubes-os.org/
  4. #
  5. # Copyright (C) 2016 Marta Marczykowska-Górecka
  6. # <marmarta@invisiblethingslab.com>
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation; either version 2 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License along
  19. # with this program; if not, write to the Free Software Foundation, Inc.,
  20. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  21. #
  22. import logging.handlers
  23. import unittest
  24. import unittest.mock
  25. import gc
  26. import asyncio
  27. from PyQt5 import QtTest, QtCore, QtWidgets
  28. from qubesadmin import Qubes
  29. import qubesmanager.settings as vm_settings
  30. from qubesmanager.tests import init_qtapp
  31. class VMSettingsTest(unittest.TestCase):
  32. def setUp(self):
  33. super(VMSettingsTest, self).setUp()
  34. self.qtapp, self.loop = init_qtapp()
  35. self.mock_qprogress = unittest.mock.patch(
  36. 'PyQt5.QtWidgets.QProgressDialog')
  37. self.mock_qprogress.start()
  38. self.addCleanup(self.mock_qprogress.stop)
  39. self.qapp = Qubes()
  40. if "testvm" in self.qapp.domains:
  41. del self.qapp.domains["testvm"]
  42. def tearDown(self):
  43. del self.qapp.domains["testvm"]
  44. self.qtapp.processEvents()
  45. yield from self.loop.sleep(1)
  46. super(VMSettingsTest, self).tearDown()
  47. def test_00_load_correct_tab(self):
  48. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "red")
  49. self.dialog = vm_settings.VMSettingsWindow(
  50. self.vm, self.qtapp, "basic")
  51. self.assertTrue(
  52. self.dialog.tabWidget.currentWidget() is self.dialog.basic_tab)
  53. self.dialog.deleteLater()
  54. self.dialog = vm_settings.VMSettingsWindow(
  55. self.vm, self.qtapp, "advanced")
  56. self.assertTrue(
  57. self.dialog.tabWidget.currentWidget() is self.dialog.advanced_tab)
  58. self.dialog.deleteLater()
  59. self.dialog = vm_settings.VMSettingsWindow(
  60. self.vm, self.qtapp, "firewall")
  61. self.assertTrue(
  62. self.dialog.tabWidget.currentWidget() is self.dialog.firewall_tab)
  63. self.dialog.deleteLater()
  64. self.dialog = vm_settings.VMSettingsWindow(
  65. self.vm, self.qtapp, "devices")
  66. self.assertTrue(
  67. self.dialog.tabWidget.currentWidget() is self.dialog.devices_tab)
  68. self.dialog.deleteLater()
  69. self.dialog = vm_settings.VMSettingsWindow(
  70. self.vm, self.qtapp, "applications")
  71. self.assertTrue(
  72. self.dialog.tabWidget.currentWidget() is self.dialog.apps_tab)
  73. self.dialog.deleteLater()
  74. self.dialog = vm_settings.VMSettingsWindow(
  75. self.vm, self.qtapp, "services")
  76. self.assertTrue(
  77. self.dialog.tabWidget.currentWidget() is self.dialog.services_tab)
  78. self.dialog.deleteLater()
  79. def test_01_basic_tab_default(self):
  80. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  81. # set the vm to have a default template and netvm
  82. self.dialog = vm_settings.VMSettingsWindow(
  83. self.vm, self.qtapp, "basic")
  84. self.assertEqual(self.dialog.vmname.text(), "testvm",
  85. "Name displayed incorrectly")
  86. self.assertTrue("blue" in self.dialog.vmlabel.currentText(),
  87. "Incorrect label displayed")
  88. displayed_template = self.dialog.template_name.currentText()
  89. correct_template = self.vm.template.name
  90. self.assertTrue("current" in displayed_template,
  91. "Template incorrectly not shown as current")
  92. self.assertTrue(correct_template in displayed_template,
  93. "Template not displayed correctly")
  94. displayed_netvm = self.dialog.netVM.currentText()
  95. correct_netvm = self.vm.netvm.name
  96. self.assertTrue("current" in displayed_netvm,
  97. "NetVM incorrectly not shown as current")
  98. self.assertTrue(correct_netvm in displayed_netvm,
  99. "NetVM not displayed correctly")
  100. self.assertEqual(self.dialog.include_in_backups.isChecked(),
  101. self.vm.include_in_backups,
  102. "Incorrect 'include in backups' state")
  103. self.assertEqual(self.dialog.run_in_debug_mode.isChecked(),
  104. self.vm.debug,
  105. "Incorrect 'run in debug mode' state")
  106. self.assertEqual(self.dialog.autostart_vm.isChecked(),
  107. self.vm.autostart,
  108. "Incorrect 'autostart' state")
  109. self.assertEqual(self.dialog.type_label.text(),
  110. self.vm.klass,
  111. "Incorrect class displayed")
  112. self.assertEqual(self.dialog.ip_label.text(),
  113. self.vm.ip,
  114. "Incorrect IP displayed")
  115. self.assertEqual(self.dialog.netmask_label.text(),
  116. self.vm.visible_netmask,
  117. "Incorrect netmask displayed")
  118. self.assertEqual(self.dialog.gateway_label.text(),
  119. self.vm.visible_gateway,
  120. "Incorrect gateway displayed")
  121. self.assertEqual(self.dialog.max_priv_storage.value(),
  122. self.vm.volumes['private'].size // 1024 ** 2,
  123. "Incorrect max private storage size")
  124. self.assertEqual(self.dialog.root_resize.value(),
  125. self.vm.volumes['root'].size // 1024 ** 2,
  126. "Incorrect max private root size")
  127. def test_02_basic_tab_nones(self):
  128. self.vm = self.qapp.add_new_vm("StandaloneVM", "testvm", "blue")
  129. # set the vm to have a default template and netvm
  130. self.vm.netvm = None
  131. self.dialog = vm_settings.VMSettingsWindow(
  132. self.vm, self.qtapp, "basic")
  133. self.assertEqual("", self.dialog.template_name.currentText(),
  134. "No template incorrectly displayed")
  135. displayed_netvm = self.dialog.netVM.currentText()
  136. self.assertTrue("current" in displayed_netvm,
  137. "None NetVM incorrectly not shown as current")
  138. self.assertTrue("none" in displayed_netvm,
  139. "None NetVM not displayed correctly")
  140. self.assertEqual(self.dialog.type_label.text(), "StandaloneVM",
  141. "Type displayed incorrectly for standaloneVM")
  142. self.assertEqual(self.dialog.ip_label.text(),
  143. "---",
  144. "Incorrect IP displayed")
  145. self.assertEqual(self.dialog.netmask_label.text(),
  146. "---",
  147. "Incorrect netmask displayed")
  148. self.assertEqual(self.dialog.gateway_label.text(),
  149. "---",
  150. "Incorrect gateway displayed")
  151. @unittest.expectedFailure
  152. def test_03_change_label(self):
  153. # this test fails due to error where we check whether label is visible
  154. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  155. self.dialog = vm_settings.VMSettingsWindow(
  156. self.vm, self.qtapp, "basic")
  157. new_label = self._set_noncurrent(self.dialog.vmlabel)
  158. self._click_ok()
  159. self.assertEqual(str(self.vm.label), new_label,
  160. "Label is not set correctly")
  161. def test_04_change_template(self):
  162. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  163. self.dialog = vm_settings.VMSettingsWindow(
  164. self.vm, self.qtapp, "basic")
  165. new_template = self._set_noncurrent(self.dialog.template_name)
  166. self._click_ok()
  167. self.assertEqual(self.vm.template.name, new_template,
  168. "Template is not set correctly")
  169. def test_05_change_networking(self):
  170. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  171. self.dialog = vm_settings.VMSettingsWindow(
  172. self.vm, self.qtapp, "basic")
  173. new_netvm = self._set_noncurrent(self.dialog.netVM)
  174. self._click_ok()
  175. self.assertEqual(self.vm.netvm.name, new_netvm,
  176. "NetVM is not set correctly")
  177. def test_06_change_networking_none(self):
  178. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  179. self.dialog = vm_settings.VMSettingsWindow(
  180. self.vm, self.qtapp, "basic")
  181. self._set_none(self.dialog.netVM)
  182. self._click_ok()
  183. self.assertIsNone(self.vm.netvm,
  184. "None netVM is not set correctly")
  185. def test_07_change_networking_to_default(self):
  186. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  187. for vm in self.qapp.domains:
  188. if getattr(vm, 'provides_network', False)\
  189. and vm != self.qapp.default_netvm:
  190. self.vm.netvm = vm
  191. break
  192. self.dialog = vm_settings.VMSettingsWindow(
  193. self.vm, self.qtapp, "basic")
  194. new_netvm = self._set_default(self.dialog.netVM)
  195. self._click_ok()
  196. self.assertTrue(self.vm.netvm.name in new_netvm,
  197. "NetVM is not set correctly")
  198. self.assertTrue(self.vm.property_is_default('netvm'))
  199. @unittest.expectedFailure
  200. def test_08_basic_checkboxes_true(self):
  201. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  202. self.dialog = vm_settings.VMSettingsWindow(
  203. self.vm, self.qtapp, "basic")
  204. self.dialog.include_in_backups.setChecked(True)
  205. self.dialog.autostart_vm.setChecked(True)
  206. self.dialog.run_in_debug_mode.setChecked(True)
  207. self._click_ok()
  208. self.assertTrue(self.vm.include_in_backups,
  209. "Include in backups not set to true")
  210. self.assertTrue(self.vm.autostart,
  211. "Autostart not set to true")
  212. self.assertTrue(self.vm.debug,
  213. "Debug mode not set to true")
  214. @unittest.expectedFailure
  215. def test_09_basic_checkboxes_false(self):
  216. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  217. self.dialog = vm_settings.VMSettingsWindow(
  218. self.vm, self.qtapp, "basic")
  219. self.dialog.include_in_backups.setChecked(False)
  220. self.dialog.autostart_vm.setChecked(False)
  221. self.dialog.run_in_debug_mode.setChecked(False)
  222. self._click_ok()
  223. self.assertFalse(self.vm.include_in_backups,
  224. "Include in backups not set to false")
  225. self.assertFalse(self.vm.autostart,
  226. "Autostart not set to false")
  227. self.assertFalse(self.vm.debug,
  228. "Debug mode not set to false")
  229. def test_10_increase_private_storage(self):
  230. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  231. self.dialog = vm_settings.VMSettingsWindow(
  232. self.vm, self.qtapp, "basic")
  233. current_storage = self.vm.volumes['private'].size // 1024**2
  234. new_storage = current_storage + 512
  235. self.dialog.max_priv_storage.setValue(new_storage)
  236. self._click_ok()
  237. self.assertEqual(self.vm.volumes['private'].size // 1024**2,
  238. new_storage)
  239. # TODO are dependencies correctly processed
  240. @unittest.mock.patch('PyQt5.QtWidgets.QProgressDialog')
  241. @unittest.mock.patch('PyQt5.QtWidgets.QInputDialog.getText')
  242. @unittest.mock.patch('qubesmanager.settings.RenameVMThread')
  243. def test_11_rename_vm(self, mock_thread, mock_input, _):
  244. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  245. self.dialog = vm_settings.VMSettingsWindow(
  246. self.vm, self.qtapp, "basic")
  247. self.assertTrue(self.dialog.rename_vm_button.isEnabled())
  248. mock_input.return_value = ("testvm2", True)
  249. self.dialog.rename_vm_button.click()
  250. mock_thread.assert_called_with(self.vm, "testvm2", unittest.mock.ANY)
  251. mock_thread().start.assert_called_with()
  252. # TODO: thread tests for rename
  253. @unittest.mock.patch('PyQt5.QtWidgets.QProgressDialog')
  254. @unittest.mock.patch('PyQt5.QtWidgets.QInputDialog.getText')
  255. @unittest.mock.patch('qubesmanager.common_threads.CloneVMThread')
  256. def test_12_clone_vm(self, mock_thread, mock_input, _):
  257. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  258. self.dialog = vm_settings.VMSettingsWindow(
  259. self.vm, self.qtapp, "basic")
  260. self.assertTrue(self.dialog.clone_vm_button.isEnabled())
  261. mock_input.return_value = ("testvm2", True)
  262. self.dialog.clone_vm_button.click()
  263. mock_thread.assert_called_with(self.vm, "testvm2")
  264. mock_thread().start.assert_called_with()
  265. @unittest.mock.patch('PyQt5.QtWidgets.QMessageBox.warning')
  266. @unittest.mock.patch('PyQt5.QtWidgets.QProgressDialog')
  267. @unittest.mock.patch('PyQt5.QtWidgets.QInputDialog.getText')
  268. @unittest.mock.patch('qubesmanager.common_threads.RemoveVMThread')
  269. def test_13_remove_vm(self, mock_thread, mock_input, _, mock_warning):
  270. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  271. self.dialog = vm_settings.VMSettingsWindow(
  272. self.vm, self.qtapp, "basic")
  273. self.assertTrue(self.dialog.delete_vm_button.isEnabled())
  274. # try with a wrong name
  275. mock_input.return_value = ("testvm2", True)
  276. self.dialog.delete_vm_button.click()
  277. self.assertEqual(mock_warning.call_count, 1)
  278. # and now correct one
  279. mock_input.return_value = ("testvm", True)
  280. self.dialog.delete_vm_button.click()
  281. mock_thread.assert_called_with(self.vm)
  282. mock_thread().start.assert_called_with()
  283. # Advanced Tab
  284. def test_20_advanced_loads(self):
  285. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  286. self.dialog = vm_settings.VMSettingsWindow(
  287. self.vm, self.qtapp, "advanced")
  288. self.assertEqual(self.dialog.init_mem.value(), self.vm.memory,
  289. "Incorrect initial memory")
  290. # default maxmem
  291. self.assertEqual(self.dialog.max_mem_size.value(),
  292. self.vm.property_get_default('maxmem'),
  293. "Maxmem incorrectly displayed for default value")
  294. self.assertEqual(self.dialog.vcpus.value(), self.vm.vcpus,
  295. "Incorrect number of VCPUs")
  296. self.assertTrue(self.dialog.include_in_balancing.isChecked(),
  297. "Include in memory balancing incorrectly not checked")
  298. # kernel
  299. self.assertTrue(self.vm.kernel in self.dialog.kernel.currentText(),
  300. "Kernel displayed incorrectly")
  301. # default dispvm
  302. self.assertTrue(
  303. str(self.vm.default_dispvm) in
  304. self.dialog.default_dispvm.currentText(),
  305. "Default dispVM incorrectly displayed")
  306. self.assertEqual(self.vm.template_for_dispvms,
  307. self.dialog.dvm_template_checkbox.isChecked(),
  308. "Incorrectly shown to be template for dispvms")
  309. # virtmode
  310. self.assertTrue("default" in self.dialog.virt_mode.currentText())
  311. self.assertTrue("PVH" in self.dialog.virt_mode.currentText())
  312. def test_21_nondefaultmaxmem(self):
  313. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  314. self.vm.maxmem = 5000
  315. self.dialog = vm_settings.VMSettingsWindow(
  316. self.vm, self.qtapp, "advanced")
  317. self.assertEqual(self.dialog.max_mem_size.value(), 5000)
  318. self.dialog.include_in_balancing.setChecked(False)
  319. self._click_ok()
  320. self.assertEqual(self.vm.maxmem, 0)
  321. self.dialog.deleteLater()
  322. self.dialog = vm_settings.VMSettingsWindow(
  323. self.vm, self.qtapp, "advanced")
  324. self.assertFalse(self.dialog.include_in_balancing.isChecked())
  325. self.dialog.include_in_balancing.setChecked(True)
  326. self.assertEqual(self.dialog.max_mem_size.value(), 5000)
  327. self._click_ok()
  328. self.assertEqual(self.vm.maxmem, 5000)
  329. def test_22_initmem(self):
  330. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  331. self.vm.memory = 500
  332. self.dialog = vm_settings.VMSettingsWindow(
  333. self.vm, self.qtapp, "advanced")
  334. self.assertEqual(self.dialog.init_mem.value(), 500,
  335. "Incorrect initial memory")
  336. self.dialog.init_mem.setValue(600)
  337. self._click_ok()
  338. self.assertEqual(self.vm.memory, 600, "Setting initial memory failed")
  339. def test_23_vcpus(self):
  340. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  341. self.vm.vcpus = 1
  342. self.dialog = vm_settings.VMSettingsWindow(
  343. self.vm, self.qtapp, "advanced")
  344. self.assertEqual(self.dialog.vcpus.value(), 1,
  345. "Incorrect number of VCPUs")
  346. self.dialog.vcpus.setValue(2)
  347. self._click_ok()
  348. self.assertEqual(self.vm.vcpus, 2,
  349. "Incorrect number of VCPUs")
  350. def test_24_kernel(self):
  351. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  352. self.dialog = vm_settings.VMSettingsWindow(
  353. self.vm, self.qtapp, "advanced")
  354. new_kernel = self._set_noncurrent(self.dialog.kernel)
  355. self._click_ok()
  356. self.assertEqual(self.vm.kernel, new_kernel)
  357. self.dialog.deleteLater()
  358. self.dialog = vm_settings.VMSettingsWindow(
  359. self.vm, self.qtapp, "advanced")
  360. self._set_default(self.dialog.kernel)
  361. self._click_ok()
  362. self.assertTrue(self.vm.property_is_default('kernel'))
  363. def test_25_virtmode_change(self):
  364. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  365. modes = ["HVM", "PVH", "PV"]
  366. for mode in modes:
  367. self.dialog = vm_settings.VMSettingsWindow(
  368. self.vm, self.qtapp, "advanced")
  369. self._set_value(self.dialog.virt_mode, mode)
  370. self._click_ok()
  371. self.assertEqual(self.vm.virt_mode.upper(), mode)
  372. self.dialog.deleteLater()
  373. self.dialog = vm_settings.VMSettingsWindow(
  374. self.vm, self.qtapp, "advanced")
  375. self._set_default(self.dialog.virt_mode)
  376. self._click_ok()
  377. self.assertTrue(self.vm.property_is_default('virt_mode'))
  378. def test_26_default_dispvm(self):
  379. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  380. self.dialog = vm_settings.VMSettingsWindow(
  381. self.vm, self.qtapp, "advanced")
  382. new_dvm = self._set_noncurrent(self.dialog.default_dispvm)
  383. self._click_ok()
  384. self.assertEqual(self.vm.default_dispvm.name, new_dvm)
  385. self.dialog.deleteLater()
  386. self.dialog = vm_settings.VMSettingsWindow(
  387. self.vm, self.qtapp, "advanced")
  388. self._set_default(self.dialog.default_dispvm)
  389. self._click_ok()
  390. self.assertTrue(self.vm.property_is_default('default_dispvm'))
  391. @unittest.mock.patch('subprocess.check_call')
  392. def test_27_boot_cdrom(self, mock_call):
  393. self.vm = self.qapp.add_new_vm("AppVM", "testvm", "blue")
  394. self.dialog = vm_settings.VMSettingsWindow(
  395. self.vm, self.qtapp, "advanced")
  396. self.dialog.boot_from_device_button.click()
  397. mock_call.assert_called_with(['qubes-vm-boot-from-device', "testvm"])
  398. def _click_ok(self):
  399. okwidget = self.dialog.buttonBox.button(
  400. self.dialog.buttonBox.Ok)
  401. QtTest.QTest.mouseClick(okwidget, QtCore.Qt.LeftButton)
  402. def _click_cancel(self):
  403. cancelwidget = self.dialog.buttonBox.button(
  404. self.dialog.buttonBox.Cancel)
  405. QtTest.QTest.mouseClick(cancelwidget, QtCore.Qt.LeftButton)
  406. def _set_noncurrent(self, widget):
  407. if widget.count() < 2:
  408. self.skipTest("not enough choices for " + widget.objectName())
  409. widget.setCurrentIndex(0)
  410. while widget.currentText().endswith("(current)") \
  411. or widget.currentText().startswith("(none)"):
  412. widget.setCurrentIndex(widget.currentIndex() + 1)
  413. return widget.currentText()
  414. def _set_default(self, widget):
  415. if widget.count() < 2:
  416. self.skipTest("not enough choices for " + widget.objectName())
  417. widget.setCurrentIndex(0)
  418. while "default" not in widget.currentText():
  419. widget.setCurrentIndex(widget.currentIndex() + 1)
  420. return widget.currentText()
  421. def _set_none(self, widget):
  422. if widget.count() < 2:
  423. self.skipTest("not enough choices for " + widget.objectName())
  424. widget.setCurrentIndex(0)
  425. while "none" not in widget.currentText():
  426. widget.setCurrentIndex(widget.currentIndex() + 1)
  427. return widget.currentText()
  428. def _set_value(self, widget, value):
  429. if widget.count() < 2:
  430. self.skipTest("not enough choices for " + widget.objectName())
  431. widget.setCurrentIndex(0)
  432. while value != widget.currentText():
  433. widget.setCurrentIndex(widget.currentIndex() + 1)
  434. return widget.currentText()
  435. if __name__ == "__main__":
  436. ha_syslog = logging.handlers.SysLogHandler('/dev/log')
  437. ha_syslog.setFormatter(
  438. logging.Formatter('%(name)s[%(process)d]: %(message)s'))
  439. logging.root.addHandler(ha_syslog)
  440. unittest.main()