qvm_template_postprocess.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. # -*- encoding: utf8 -*-
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2017 Marek Marczykowski-Górecki
  6. # <marmarek@invisiblethingslab.com>
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU Lesser General Public License as published by
  10. # the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Lesser General Public License along
  19. # with this program; if not, see <http://www.gnu.org/licenses/>.
  20. import asyncio
  21. import os
  22. import subprocess
  23. import tempfile
  24. from unittest import mock
  25. import qubesadmin.tests
  26. import qubesadmin.tools.qvm_template_postprocess
  27. class QubesLocalMock(qubesadmin.tests.QubesTest):
  28. def __init__(self):
  29. super(QubesLocalMock, self).__init__()
  30. self.__class__ = qubesadmin.app.QubesLocal
  31. qubesd_call = qubesadmin.tests.QubesTest.qubesd_call
  32. run_service = qubesadmin.tests.QubesTest.run_service
  33. class TC_00_qvm_template_postprocess(qubesadmin.tests.QubesTestCase):
  34. def setUp(self):
  35. super(TC_00_qvm_template_postprocess, self).setUp()
  36. self.source_dir = tempfile.TemporaryDirectory()
  37. def tearDown(self):
  38. try:
  39. self.source_dir.cleanup()
  40. except FileNotFoundError:
  41. pass
  42. super(TC_00_qvm_template_postprocess, self).tearDown()
  43. def test_000_import_root_img_raw(self):
  44. root_img = os.path.join(self.source_dir.name, 'root.img')
  45. volume_data = b'volume data'
  46. with open(root_img, 'wb') as f:
  47. f.write(volume_data)
  48. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  49. b'0\0test-vm class=TemplateVM state=Halted\n'
  50. self.app.expected_calls[('test-vm', 'admin.vm.volume.List', None,
  51. None)] = \
  52. b'0\0root\nprivate\nvolatile\nkernel\n'
  53. self.app.expected_calls[
  54. ('test-vm', 'admin.vm.volume.Info', 'root', None)] = \
  55. b'0\x00pool=lvm\n' \
  56. b'vid=qubes_dom0/vm-test-vm-root\n' \
  57. b'size=10737418240\n' \
  58. b'usage=0\n' \
  59. b'rw=True\n' \
  60. b'source=\n' \
  61. b'save_on_stop=True\n' \
  62. b'snap_on_start=False\n' \
  63. b'revisions_to_keep=3\n' \
  64. b'is_outdated=False\n'
  65. self.app.expected_calls[('test-vm', 'admin.vm.volume.Resize', 'root',
  66. str(len(volume_data)).encode())] = \
  67. b'0\0'
  68. self.app.expected_calls[('test-vm', 'admin.vm.volume.Import', 'root',
  69. volume_data)] = b'0\0'
  70. vm = self.app.domains['test-vm']
  71. qubesadmin.tools.qvm_template_postprocess.import_root_img(
  72. vm, self.source_dir.name)
  73. self.assertAllCalled()
  74. def test_001_import_root_img_tar(self):
  75. root_img = os.path.join(self.source_dir.name, 'root.img')
  76. volume_data = b'volume data' * 1000
  77. with open(root_img, 'wb') as f:
  78. f.write(volume_data)
  79. subprocess.check_call(['tar', 'cf', 'root.img.tar', 'root.img'],
  80. cwd=self.source_dir.name)
  81. subprocess.check_call(['split', '-d', '-b', '1024', 'root.img.tar',
  82. 'root.img.part.'], cwd=self.source_dir.name)
  83. os.unlink(root_img)
  84. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  85. b'0\0test-vm class=TemplateVM state=Halted\n'
  86. self.app.expected_calls[
  87. ('test-vm', 'admin.vm.volume.Info', 'root', None)] = \
  88. b'0\x00pool=lvm\n' \
  89. b'vid=qubes_dom0/vm-test-vm-root\n' \
  90. b'size=10737418240\n' \
  91. b'usage=0\n' \
  92. b'rw=True\n' \
  93. b'source=\n' \
  94. b'save_on_stop=True\n' \
  95. b'snap_on_start=False\n' \
  96. b'revisions_to_keep=3\n' \
  97. b'is_outdated=False\n'
  98. self.app.expected_calls[('test-vm', 'admin.vm.volume.List', None,
  99. None)] = \
  100. b'0\0root\nprivate\nvolatile\nkernel\n'
  101. self.app.expected_calls[('test-vm', 'admin.vm.volume.Resize', 'root',
  102. str(len(volume_data)).encode())] = \
  103. b'0\0'
  104. self.app.expected_calls[('test-vm', 'admin.vm.volume.Import', 'root',
  105. volume_data)] = b'0\0'
  106. vm = self.app.domains['test-vm']
  107. qubesadmin.tools.qvm_template_postprocess.import_root_img(
  108. vm, self.source_dir.name)
  109. self.assertAllCalled()
  110. def test_002_import_root_img_no_overwrite(self):
  111. self.app.qubesd_connection_type = 'socket'
  112. template_dir = os.path.join(self.source_dir.name, 'vm-templates',
  113. 'test-vm')
  114. os.makedirs(template_dir)
  115. root_img = os.path.join(template_dir, 'root.img')
  116. volume_data = b'volume data'
  117. with open(root_img, 'wb') as f:
  118. f.write(volume_data)
  119. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  120. b'0\0test-vm class=TemplateVM state=Halted\n'
  121. self.app.expected_calls[
  122. ('test-vm', 'admin.vm.volume.List', None, None)] = \
  123. b'0\0root\nprivate\nvolatile\nkernel\n'
  124. self.app.expected_calls[
  125. ('test-vm', 'admin.vm.volume.Info', 'root', None)] = \
  126. b'0\x00pool=default\n' \
  127. b'vid=vm-templates/test-vm/root\n' \
  128. b'size=10737418240\n' \
  129. b'usage=0\n' \
  130. b'rw=True\n' \
  131. b'source=\n' \
  132. b'save_on_stop=True\n' \
  133. b'snap_on_start=False\n' \
  134. b'revisions_to_keep=3\n' \
  135. b'is_outdated=False\n'
  136. self.app.expected_calls[
  137. ('dom0', 'admin.pool.List', None, None)] = \
  138. b'0\0default\n'
  139. self.app.expected_calls[
  140. ('dom0', 'admin.pool.Info', 'default', None)] = \
  141. b'0\0driver=file\ndir_path=' + self.source_dir.name.encode() + b'\n'
  142. vm = self.app.domains['test-vm']
  143. qubesadmin.tools.qvm_template_postprocess.import_root_img(
  144. vm, template_dir)
  145. self.assertAllCalled()
  146. def test_005_reset_private_img(self):
  147. self.app.qubesd_connection_type = 'socket'
  148. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  149. b'0\0test-vm class=TemplateVM state=Halted\n'
  150. self.app.expected_calls[
  151. ('test-vm', 'admin.vm.volume.List', None, None)] = \
  152. b'0\0root\nprivate\nvolatile\nkernel\n'
  153. self.app.expected_calls[('test-vm', 'admin.vm.volume.Clear', 'private',
  154. None)] = b'0\0'
  155. vm = self.app.domains['test-vm']
  156. qubesadmin.tools.qvm_template_postprocess.reset_private_img(vm)
  157. self.assertAllCalled()
  158. def test_010_import_appmenus(self):
  159. default_menu_items = [
  160. 'org.gnome.Terminal.desktop',
  161. 'firefox.desktop']
  162. menu_items = [
  163. 'org.gnome.Terminal.desktop',
  164. 'org.gnome.Software.desktop',
  165. 'gnome-control-center.desktop']
  166. netvm_menu_items = [
  167. 'org.gnome.Terminal.desktop',
  168. 'nm-connection-editor.desktop']
  169. with open(os.path.join(self.source_dir.name,
  170. 'vm-whitelisted-appmenus.list'), 'w') as f:
  171. for entry in default_menu_items:
  172. f.write(entry + '\n')
  173. with open(os.path.join(self.source_dir.name,
  174. 'whitelisted-appmenus.list'), 'w') as f:
  175. for entry in menu_items:
  176. f.write(entry + '\n')
  177. with open(os.path.join(self.source_dir.name,
  178. 'netvm-whitelisted-appmenus.list'), 'w') as f:
  179. for entry in netvm_menu_items:
  180. f.write(entry + '\n')
  181. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  182. b'0\0test-vm class=TemplateVM state=Halted\n'
  183. self.app.expected_calls[(
  184. 'test-vm',
  185. 'admin.vm.feature.Set',
  186. 'default-menu-items',
  187. ' '.join(default_menu_items).encode())] = b'0\0'
  188. self.app.expected_calls[(
  189. 'test-vm',
  190. 'admin.vm.feature.Set',
  191. 'menu-items',
  192. ' '.join(menu_items).encode())] = b'0\0'
  193. self.app.expected_calls[(
  194. 'test-vm',
  195. 'admin.vm.feature.Set',
  196. 'netvm-menu-items',
  197. ' '.join(netvm_menu_items).encode())] = b'0\0'
  198. vm = self.app.domains['test-vm']
  199. with mock.patch('subprocess.check_call') as mock_proc:
  200. qubesadmin.tools.qvm_template_postprocess.import_appmenus(
  201. vm, self.source_dir.name)
  202. self.assertEqual(mock_proc.mock_calls, [
  203. mock.call(['qvm-appmenus',
  204. '--set-default-whitelist=' + os.path.join(self.source_dir.name,
  205. 'vm-whitelisted-appmenus.list'), 'test-vm']),
  206. mock.call(['qvm-appmenus', '--set-whitelist=' + os.path.join(
  207. self.source_dir.name, 'whitelisted-appmenus.list'), 'test-vm']),
  208. ])
  209. self.assertAllCalled()
  210. @mock.patch('grp.getgrnam')
  211. @mock.patch('os.getuid')
  212. def test_011_import_appmenus_as_root(self, mock_getuid, mock_getgrnam):
  213. default_menu_items = [
  214. 'org.gnome.Terminal.desktop',
  215. 'firefox.desktop']
  216. menu_items = [
  217. 'org.gnome.Terminal.desktop',
  218. 'org.gnome.Software.desktop',
  219. 'gnome-control-center.desktop']
  220. netvm_menu_items = [
  221. 'org.gnome.Terminal.desktop',
  222. 'nm-connection-editor.desktop']
  223. with open(os.path.join(self.source_dir.name,
  224. 'vm-whitelisted-appmenus.list'), 'w') as f:
  225. for entry in default_menu_items:
  226. f.write(entry + '\n')
  227. with open(os.path.join(self.source_dir.name,
  228. 'whitelisted-appmenus.list'), 'w') as f:
  229. for entry in menu_items:
  230. f.write(entry + '\n')
  231. with open(os.path.join(self.source_dir.name,
  232. 'netvm-whitelisted-appmenus.list'), 'w') as f:
  233. for entry in netvm_menu_items:
  234. f.write(entry + '\n')
  235. self.app.expected_calls[(
  236. 'test-vm',
  237. 'admin.vm.feature.Set',
  238. 'default-menu-items',
  239. ' '.join(default_menu_items).encode())] = b'0\0'
  240. self.app.expected_calls[(
  241. 'test-vm',
  242. 'admin.vm.feature.Set',
  243. 'menu-items',
  244. ' '.join(menu_items).encode())] = b'0\0'
  245. self.app.expected_calls[(
  246. 'test-vm',
  247. 'admin.vm.feature.Set',
  248. 'netvm-menu-items',
  249. ' '.join(netvm_menu_items).encode())] = b'0\0'
  250. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  251. b'0\0test-vm class=TemplateVM state=Halted\n'
  252. mock_getuid.return_value = 0
  253. mock_getgrnam.configure_mock(**{
  254. 'return_value.gr_mem.__getitem__.return_value': 'user'
  255. })
  256. vm = self.app.domains['test-vm']
  257. with mock.patch('subprocess.check_call') as mock_proc:
  258. qubesadmin.tools.qvm_template_postprocess.import_appmenus(
  259. vm, self.source_dir.name)
  260. self.assertEqual(mock_proc.mock_calls, [
  261. mock.call(['runuser', '-u', 'user', '--', 'env', 'DISPLAY=:0',
  262. 'qvm-appmenus',
  263. '--set-default-whitelist=' + os.path.join(self.source_dir.name,
  264. 'vm-whitelisted-appmenus.list'), 'test-vm']),
  265. mock.call(['runuser', '-u', 'user', '--', 'env', 'DISPLAY=:0',
  266. 'qvm-appmenus', '--set-whitelist=' + os.path.join(
  267. self.source_dir.name, 'whitelisted-appmenus.list'), 'test-vm']),
  268. ])
  269. self.assertAllCalled()
  270. @mock.patch('grp.getgrnam')
  271. @mock.patch('os.getuid')
  272. def test_012_import_appmenus_missing_user(self, mock_getuid, mock_getgrnam):
  273. with open(os.path.join(self.source_dir.name,
  274. 'vm-whitelisted-appmenus.list'), 'w') as f:
  275. f.write('org.gnome.Terminal.desktop\n')
  276. f.write('firefox.desktop\n')
  277. with open(os.path.join(self.source_dir.name,
  278. 'whitelisted-appmenus.list'), 'w') as f:
  279. f.write('org.gnome.Terminal.desktop\n')
  280. f.write('org.gnome.Software.desktop\n')
  281. f.write('gnome-control-center.desktop\n')
  282. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  283. b'0\0test-vm class=TemplateVM state=Halted\n'
  284. mock_getuid.return_value = 0
  285. mock_getgrnam.side_effect = KeyError
  286. vm = self.app.domains['test-vm']
  287. with mock.patch('subprocess.check_call') as mock_proc:
  288. qubesadmin.tools.qvm_template_postprocess.import_appmenus(
  289. vm, self.source_dir.name)
  290. self.assertEqual(mock_proc.mock_calls, [])
  291. self.assertAllCalled()
  292. def add_new_vm_side_effect(self, *args, **kwargs):
  293. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  294. b'0\0test-vm class=TemplateVM state=Halted\n'
  295. self.app.domains.clear_cache()
  296. return self.app.domains['test-vm']
  297. @asyncio.coroutine
  298. def wait_for_shutdown(self, vm):
  299. pass
  300. @mock.patch('qubesadmin.tools.qvm_template_postprocess.import_appmenus')
  301. @mock.patch('qubesadmin.tools.qvm_template_postprocess.import_root_img')
  302. def test_020_post_install(self, mock_import_root_img,
  303. mock_import_appmenus):
  304. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  305. b'0\0'
  306. self.app.add_new_vm = mock.Mock(side_effect=self.add_new_vm_side_effect)
  307. self.app.expected_calls[
  308. ('test-vm', 'admin.vm.property.Set', 'netvm', b'')] = b'0\0'
  309. self.app.expected_calls[
  310. ('test-vm', 'admin.vm.property.Set', 'installed_by_rpm', b'True')] \
  311. = b'0\0'
  312. self.app.expected_calls[
  313. ('test-vm', 'admin.vm.property.Reset', 'netvm', None)] = b'0\0'
  314. self.app.expected_calls[
  315. ('test-vm', 'admin.vm.feature.Set', 'qrexec', b'1')] = b'0\0'
  316. self.app.expected_calls[
  317. ('test-vm', 'admin.vm.Start', None, None)] = b'0\0'
  318. self.app.expected_calls[
  319. ('test-vm', 'admin.vm.Shutdown', None, None)] = b'0\0'
  320. if qubesadmin.tools.qvm_template_postprocess.have_events:
  321. patch_domain_shutdown = mock.patch(
  322. 'qubesadmin.events.utils.wait_for_domain_shutdown')
  323. self.addCleanup(patch_domain_shutdown.stop)
  324. mock_domain_shutdown = patch_domain_shutdown.start()
  325. mock_domain_shutdown.side_effect = self.wait_for_shutdown
  326. else:
  327. self.app.expected_calls[
  328. ('test-vm', 'admin.vm.List', None, None)] = \
  329. b'0\0test-vm class=TemplateVM state=Halted\n'
  330. asyncio.set_event_loop(asyncio.new_event_loop())
  331. ret = qubesadmin.tools.qvm_template_postprocess.main([
  332. '--really', 'post-install', 'test-vm', self.source_dir.name],
  333. app=self.app)
  334. self.assertEqual(ret, 0)
  335. self.app.add_new_vm.assert_called_once_with('TemplateVM',
  336. name='test-vm', label='black', pool=None)
  337. mock_import_root_img.assert_called_once_with(self.app.domains[
  338. 'test-vm'], self.source_dir.name)
  339. mock_import_appmenus.assert_called_once_with(self.app.domains[
  340. 'test-vm'], self.source_dir.name)
  341. if qubesadmin.tools.qvm_template_postprocess.have_events:
  342. mock_domain_shutdown.assert_called_once_with([self.app.domains[
  343. 'test-vm']])
  344. self.assertEqual(self.app.service_calls, [
  345. ('test-vm', 'qubes.PostInstall', {}),
  346. ('test-vm', 'qubes.PostInstall', b''),
  347. ])
  348. self.assertAllCalled()
  349. @mock.patch('qubesadmin.tools.qvm_template_postprocess.import_appmenus')
  350. @mock.patch('qubesadmin.tools.qvm_template_postprocess.import_root_img')
  351. @mock.patch('qubesadmin.tools.qvm_template_postprocess.reset_private_img')
  352. def test_021_post_install_reinstall(self, mock_reset_private_img,
  353. mock_import_root_img, mock_import_appmenus):
  354. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  355. b'0\0test-vm class=TemplateVM state=Halted\n'
  356. self.app.add_new_vm = mock.Mock()
  357. self.app.expected_calls[
  358. ('test-vm', 'admin.vm.property.Set', 'netvm', b'')] = b'0\0'
  359. self.app.expected_calls[
  360. ('test-vm', 'admin.vm.property.Set', 'installed_by_rpm', b'True')] \
  361. = b'0\0'
  362. self.app.expected_calls[
  363. ('test-vm', 'admin.vm.property.Reset', 'netvm', None)] = b'0\0'
  364. self.app.expected_calls[
  365. ('test-vm', 'admin.vm.feature.Set', 'qrexec', b'1')] = b'0\0'
  366. self.app.expected_calls[
  367. ('test-vm', 'admin.vm.Start', None, None)] = b'0\0'
  368. self.app.expected_calls[
  369. ('test-vm', 'admin.vm.Shutdown', None, None)] = b'0\0'
  370. if qubesadmin.tools.qvm_template_postprocess.have_events:
  371. patch_domain_shutdown = mock.patch(
  372. 'qubesadmin.events.utils.wait_for_domain_shutdown')
  373. self.addCleanup(patch_domain_shutdown.stop)
  374. mock_domain_shutdown = patch_domain_shutdown.start()
  375. mock_domain_shutdown.side_effect = self.wait_for_shutdown
  376. else:
  377. self.app.expected_calls[
  378. ('test-vm', 'admin.vm.List', None, None)] = \
  379. b'0\0test-vm class=TemplateVM state=Halted\n'
  380. asyncio.set_event_loop(asyncio.new_event_loop())
  381. ret = qubesadmin.tools.qvm_template_postprocess.main([
  382. '--really', 'post-install', 'test-vm', self.source_dir.name],
  383. app=self.app)
  384. self.assertEqual(ret, 0)
  385. self.assertFalse(self.app.add_new_vm.called)
  386. mock_import_root_img.assert_called_once_with(self.app.domains[
  387. 'test-vm'], self.source_dir.name)
  388. mock_reset_private_img.assert_called_once_with(self.app.domains[
  389. 'test-vm'])
  390. mock_import_appmenus.assert_called_once_with(self.app.domains[
  391. 'test-vm'], self.source_dir.name)
  392. if qubesadmin.tools.qvm_template_postprocess.have_events:
  393. mock_domain_shutdown.assert_called_once_with([self.app.domains[
  394. 'test-vm']])
  395. self.assertEqual(self.app.service_calls, [
  396. ('test-vm', 'qubes.PostInstall', {}),
  397. ('test-vm', 'qubes.PostInstall', b''),
  398. ])
  399. self.assertAllCalled()
  400. @mock.patch('qubesadmin.tools.qvm_template_postprocess.import_appmenus')
  401. @mock.patch('qubesadmin.tools.qvm_template_postprocess.import_root_img')
  402. @mock.patch('qubesadmin.tools.qvm_template_postprocess.reset_private_img')
  403. def test_022_post_install_skip_start(self, mock_reset_private_img,
  404. mock_import_root_img, mock_import_appmenus):
  405. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  406. b'0\0test-vm class=TemplateVM state=Halted\n'
  407. self.app.expected_calls[
  408. ('test-vm', 'admin.vm.property.Set', 'installed_by_rpm', b'True')] \
  409. = b'0\0'
  410. self.app.add_new_vm = mock.Mock()
  411. if qubesadmin.tools.qvm_template_postprocess.have_events:
  412. patch_domain_shutdown = mock.patch(
  413. 'qubesadmin.events.utils.wait_for_domain_shutdown')
  414. self.addCleanup(patch_domain_shutdown.stop)
  415. mock_domain_shutdown = patch_domain_shutdown.start()
  416. mock_domain_shutdown.side_effect = self.wait_for_shutdown
  417. asyncio.set_event_loop(asyncio.new_event_loop())
  418. ret = qubesadmin.tools.qvm_template_postprocess.main([
  419. '--really', '--skip-start', 'post-install', 'test-vm',
  420. self.source_dir.name],
  421. app=self.app)
  422. self.assertEqual(ret, 0)
  423. self.assertFalse(self.app.add_new_vm.called)
  424. mock_import_root_img.assert_called_once_with(self.app.domains[
  425. 'test-vm'], self.source_dir.name)
  426. mock_reset_private_img.assert_called_once_with(self.app.domains[
  427. 'test-vm'])
  428. mock_import_appmenus.assert_called_once_with(self.app.domains[
  429. 'test-vm'], self.source_dir.name)
  430. if qubesadmin.tools.qvm_template_postprocess.have_events:
  431. self.assertFalse(mock_domain_shutdown.called)
  432. self.assertEqual(self.app.service_calls, [])
  433. self.assertAllCalled()
  434. def test_030_pre_remove(self):
  435. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  436. b'0\0test-vm class=TemplateVM state=Halted\n'
  437. self.app.expected_calls[('test-vm', 'admin.vm.Remove', None, None)] = \
  438. b'0\0'
  439. self.app.expected_calls[
  440. ('test-vm', 'admin.vm.property.Set', 'installed_by_rpm', b'False')]\
  441. = b'0\0'
  442. self.app.expected_calls[
  443. ('test-vm', 'admin.vm.property.Get', 'template', None)] = \
  444. b'2\0QubesNoSuchPropertyError\0\0invalid property \'template\' of ' \
  445. b'test-vm\0'
  446. ret = qubesadmin.tools.qvm_template_postprocess.main([
  447. '--really', 'pre-remove', 'test-vm',
  448. self.source_dir.name],
  449. app=self.app)
  450. self.assertEqual(ret, 0)
  451. self.assertEqual(self.app.service_calls, [])
  452. self.assertAllCalled()
  453. def test_031_pre_remove_existing_appvm(self):
  454. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  455. b'0\0test-vm class=TemplateVM state=Halted\n' \
  456. b'test-vm2 class=AppVM state=Halted\n'
  457. self.app.expected_calls[
  458. ('test-vm', 'admin.vm.property.Get', 'template', None)] = \
  459. b'2\0QubesNoSuchPropertyError\0\0invalid property \'template\' of ' \
  460. b'test-vm\0'
  461. self.app.expected_calls[
  462. ('test-vm2', 'admin.vm.property.Get', 'template', None)] = \
  463. b'0\0default=no type=vm test-vm'
  464. with self.assertRaises(SystemExit):
  465. qubesadmin.tools.qvm_template_postprocess.main([
  466. '--really', 'pre-remove', 'test-vm',
  467. self.source_dir.name],
  468. app=self.app)
  469. self.assertEqual(self.app.service_calls, [])
  470. self.assertAllCalled()
  471. def test_040_missing_really(self):
  472. with self.assertRaises(SystemExit):
  473. qubesadmin.tools.qvm_template_postprocess.main([
  474. 'post-install', 'test-vm', self.source_dir.name],
  475. app=self.app)
  476. self.assertAllCalled()