qvm_create.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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 os
  21. import tempfile
  22. import qubesadmin.tests
  23. import qubesadmin.tests.tools
  24. import qubesadmin.tools.qvm_create
  25. class TC_00_qvm_create(qubesadmin.tests.QubesTestCase):
  26. def test_000_just_appvm(self):
  27. self.app.expected_calls[('dom0', 'admin.vm.Create.AppVM', None,
  28. b'name=new-vm label=red')] = b'0\x00'
  29. self.app.expected_calls[('dom0', 'admin.label.List', None, None)] = \
  30. b'0\x00red\nblue\n'
  31. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  32. b'0\x00new-vm class=AppVM state=Halted\n'
  33. qubesadmin.tools.qvm_create.main(['-l', 'red', 'new-vm'], app=self.app)
  34. self.assertAllCalled()
  35. def test_001_missing_vm(self):
  36. with self.assertRaises(SystemExit):
  37. with qubesadmin.tests.tools.StderrBuffer() as stderr:
  38. qubesadmin.tools.qvm_create.main(['-l', 'red'], app=self.app)
  39. self.assertIn('NAME', stderr.getvalue())
  40. self.assertAllCalled()
  41. def test_002_custom_template(self):
  42. self.app.expected_calls[('dom0', 'admin.vm.Create.AppVM',
  43. 'some-template', b'name=new-vm label=red')] = b'0\x00'
  44. self.app.expected_calls[('dom0', 'admin.label.List', None, None)] = \
  45. b'0\x00red\nblue\n'
  46. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  47. b'0\x00new-vm class=AppVM state=Halted\n'
  48. qubesadmin.tools.qvm_create.main(['-l', 'red', '-t',
  49. 'some-template', 'new-vm'], app=self.app)
  50. self.assertAllCalled()
  51. def test_003_properties(self):
  52. self.app.expected_calls[('dom0', 'admin.vm.Create.AppVM',
  53. None, b'name=new-vm label=red')] = b'0\x00'
  54. self.app.expected_calls[('dom0', 'admin.label.List', None, None)] = \
  55. b'0\x00red\nblue\n'
  56. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  57. b'0\x00new-vm class=AppVM state=Halted\n'
  58. self.app.expected_calls[('new-vm', 'admin.vm.property.Set',
  59. 'netvm', b'sys-whonix')] = b'0\x00'
  60. qubesadmin.tools.qvm_create.main(['-l', 'red', '--prop',
  61. 'netvm=sys-whonix', 'new-vm'],
  62. app=self.app)
  63. self.assertAllCalled()
  64. def test_004_pool(self):
  65. self.app.expected_calls[('dom0', 'admin.vm.CreateInPool.AppVM',
  66. None, b'name=new-vm label=red pool=some-pool')] = b'0\x00'
  67. self.app.expected_calls[('dom0', 'admin.label.List', None, None)] = \
  68. b'0\x00red\nblue\n'
  69. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  70. b'0\x00new-vm class=AppVM state=Halted\n'
  71. qubesadmin.tools.qvm_create.main(['-l', 'red', '-P', 'some-pool',
  72. 'new-vm'],
  73. app=self.app)
  74. self.assertAllCalled()
  75. def test_005_pools(self):
  76. self.app.expected_calls[('dom0', 'admin.vm.CreateInPool.AppVM',
  77. None, b'name=new-vm label=red pool:private=some-pool '
  78. b'pool:volatile=other-pool')] = b'0\x00'
  79. self.app.expected_calls[('dom0', 'admin.label.List', None, None)] = \
  80. b'0\x00red\nblue\n'
  81. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  82. b'0\x00new-vm class=AppVM state=Halted\n'
  83. qubesadmin.tools.qvm_create.main(['-l', 'red', '--pool',
  84. 'private=some-pool', '--pool', 'volatile=other-pool', 'new-vm'],
  85. app=self.app)
  86. self.assertAllCalled()
  87. def test_005_root_copy_from(self):
  88. with tempfile.NamedTemporaryFile() as root_file:
  89. root_file.file.write(b'root data')
  90. root_file.file.flush()
  91. self.app.expected_calls[('dom0', 'admin.vm.Create.AppVM',
  92. None, b'name=new-vm label=red')] = b'0\x00'
  93. self.app.expected_calls[('dom0', 'admin.label.List', None, None)] = \
  94. b'0\x00red\nblue\n'
  95. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  96. b'0\x00new-vm class=AppVM state=Halted\n'
  97. self.app.expected_calls[
  98. ('new-vm', 'admin.vm.volume.List', None, None)] = \
  99. b'0\x00root\nprivate\nvolatile\nkernel\n'
  100. self.app.expected_calls[
  101. ('new-vm', 'admin.vm.volume.Info', 'root', None)] = \
  102. b'0\x00' \
  103. b'pool=other-pool\n' \
  104. b'vid=new-vm-root\n' \
  105. b'size=10000000\n'
  106. self.app.expected_calls[
  107. ('new-vm', 'admin.vm.volume.Import', 'root', b'root data')] = \
  108. b'0\0'
  109. qubesadmin.tools.qvm_create.main(['-l', 'red',
  110. '--root-copy-from=' + root_file.name, 'new-vm'],
  111. app=self.app)
  112. self.assertAllCalled()
  113. self.assertTrue(os.path.exists(root_file.name))
  114. def test_006_root_move_from(self):
  115. with tempfile.NamedTemporaryFile(delete=False) as root_file:
  116. root_file.file.write(b'root data')
  117. root_file.file.flush()
  118. self.app.expected_calls[('dom0', 'admin.vm.Create.AppVM',
  119. None, b'name=new-vm label=red')] = b'0\x00'
  120. self.app.expected_calls[('dom0', 'admin.label.List', None, None)] = \
  121. b'0\x00red\nblue\n'
  122. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  123. b'0\x00new-vm class=AppVM state=Halted\n'
  124. self.app.expected_calls[
  125. ('new-vm', 'admin.vm.volume.List', None, None)] = \
  126. b'0\x00root\nprivate\nvolatile\nkernel\n'
  127. self.app.expected_calls[
  128. ('new-vm', 'admin.vm.volume.Info', 'root', None)] = \
  129. b'0\x00' \
  130. b'pool=other-pool\n' \
  131. b'vid=new-vm-root\n' \
  132. b'size=20000000\n'
  133. self.app.expected_calls[
  134. ('new-vm', 'admin.vm.volume.Import', 'root', b'root data')] = \
  135. b'0\0'
  136. qubesadmin.tools.qvm_create.main(['-l', 'red',
  137. '--root-move-from=' + root_file.name, 'new-vm'],
  138. app=self.app)
  139. self.assertAllCalled()
  140. self.assertFalse(os.path.exists(root_file.name))
  141. def test_007_root_move_copy_both(self):
  142. with tempfile.NamedTemporaryFile() as root_file:
  143. root_file.file.write(b'root data')
  144. root_file.file.flush()
  145. with self.assertRaises(SystemExit):
  146. qubesadmin.tools.qvm_create.main(['-l', 'red',
  147. '--root-copy-from=' + root_file.name,
  148. '--root-move-from=' + root_file.name,
  149. 'new-vm'],
  150. app=self.app)
  151. self.assertAllCalled()
  152. self.assertTrue(os.path.exists(root_file.name))
  153. def test_008_root_invalid_path(self):
  154. with self.assertRaises(SystemExit):
  155. qubesadmin.tools.qvm_create.main(['-l', 'red',
  156. '--root-copy-from=/invalid', 'new-vm'],
  157. app=self.app)
  158. self.assertAllCalled()
  159. def test_009_help_classes(self):
  160. self.app.expected_calls[('dom0', 'admin.vmclass.List',
  161. None, None)] = b'0\x00StandaloneVM\nAppVM\nTemplateVM\nDispVM\n'
  162. with qubesadmin.tests.tools.StdoutBuffer() as stdout:
  163. qubesadmin.tools.qvm_create.main(['--help-classes'],
  164. app=self.app)
  165. self.assertEqual(stdout.getvalue(),
  166. 'AppVM\nDispVM\nStandaloneVM\nTemplateVM\n')
  167. self.assertAllCalled()
  168. def test_010_root_copy_from_with_resize(self):
  169. with tempfile.NamedTemporaryFile() as root_file:
  170. root_file.file.write(b'root data')
  171. root_file.file.flush()
  172. self.app.expected_calls[('dom0', 'admin.vm.Create.AppVM',
  173. None, b'name=new-vm label=red')] = b'0\x00'
  174. self.app.expected_calls[('dom0', 'admin.label.List', None, None)] = \
  175. b'0\x00red\nblue\n'
  176. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  177. b'0\x00new-vm class=AppVM state=Halted\n'
  178. self.app.expected_calls[
  179. ('new-vm', 'admin.vm.volume.List', None, None)] = \
  180. b'0\x00root\nprivate\nvolatile\nkernel\n'
  181. self.app.expected_calls[
  182. ('new-vm', 'admin.vm.volume.Info', 'root', None)] = \
  183. b'0\x00' \
  184. b'pool=other-pool\n' \
  185. b'vid=new-vm-root\n' \
  186. b'size=2\n'
  187. self.app.expected_calls[
  188. ('new-vm', 'admin.vm.volume.Resize', 'root', b'9')] = \
  189. b'0\0'
  190. self.app.expected_calls[
  191. ('new-vm', 'admin.vm.volume.Import', 'root', b'root data')] = \
  192. b'0\0'
  193. qubesadmin.tools.qvm_create.main(['-l', 'red',
  194. '--root-copy-from=' + root_file.name, 'new-vm'],
  195. app=self.app)
  196. self.assertAllCalled()
  197. self.assertTrue(os.path.exists(root_file.name))
  198. def test_011_standalonevm(self):
  199. self.app.expected_calls[('dom0', 'admin.label.List', None, None)] = \
  200. b'0\x00red\nblue\n'
  201. self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
  202. b'0\x00template class=TemplateVM state=Halted\n' \
  203. b'new-vm class=StandaloneVM state=Halted\n'
  204. self.app.expected_calls[
  205. ('template', 'admin.vm.property.Get', 'label', None)] = \
  206. b'0\x00default=False type=label blue'
  207. self.app.expected_calls[
  208. ('template', 'admin.vm.property.Get', 'vcpus', None)] = \
  209. b'0\x00default=False type=int 2'
  210. self.app.expected_calls[
  211. ('template', 'admin.vm.property.Get', 'kernel', None)] = \
  212. b'0\x00default=True type=str kernel-version'
  213. self.app.expected_calls[
  214. ('template', 'admin.vm.property.Get', 'memory', None)] = \
  215. b'0\x00default=True type=int 400'
  216. self.app.expected_calls[
  217. ('template', 'admin.vm.property.Get', 'template', None)] = \
  218. b'2\x00QubesNoSuchPropertyError\x00\x00No such property\x00'
  219. self.app.expected_calls[
  220. ('template', 'admin.vm.property.List', None, None)] = \
  221. b'0\x00name\n' \
  222. b'label\n' \
  223. b'vcpus\n' \
  224. b'kernel\n' \
  225. b'memory\n'
  226. self.app.expected_calls[
  227. ('template', 'admin.vm.tag.List', None, None)] = \
  228. b'0\x00'
  229. self.app.expected_calls[
  230. ('template', 'admin.vm.feature.List', None, None)] = \
  231. b'0\x00'
  232. self.app.expected_calls[
  233. ('template', 'admin.vm.firewall.Get', None, None)] = \
  234. b'0\x00'
  235. self.app.expected_calls[('dom0', 'admin.vm.Create.StandaloneVM', None,
  236. b'name=new-vm label=blue')] = b'0\x00'
  237. # TODO this is weird...
  238. self.app.expected_calls[
  239. ('new-vm', 'admin.vm.property.Set', 'label', b'red')] = \
  240. b'0\x00'
  241. self.app.expected_calls[
  242. ('new-vm', 'admin.vm.property.Set', 'vcpus', b'2')] = \
  243. b'0\x00'
  244. self.app.expected_calls[
  245. ('new-vm', 'admin.vm.firewall.Set', None, b'')] = \
  246. b'0\x00'
  247. self.app.expected_calls[
  248. ('template', 'admin.vm.volume.List', None, None)] = \
  249. b'0\x00root\nprivate\nvolatile\nkernel\n'
  250. self.app.expected_calls[
  251. ('new-vm', 'admin.vm.volume.List', None, None)] = \
  252. b'0\x00root\nprivate\nvolatile\nkernel\n'
  253. self.app.expected_calls[
  254. ('new-vm', 'admin.vm.volume.Info', 'root', None)] = \
  255. b'0\x00' \
  256. b'snap_on_start=False\n' \
  257. b'save_on_stop=True\n' \
  258. b'pool=other-pool\n' \
  259. b'vid=new-vm-root\n' \
  260. b'rw=True\n' \
  261. b'size=2\n'
  262. self.app.expected_calls[
  263. ('new-vm', 'admin.vm.volume.Info', 'private', None)] = \
  264. b'0\x00' \
  265. b'snap_on_start=False\n' \
  266. b'save_on_stop=True\n' \
  267. b'pool=other-pool\n' \
  268. b'vid=new-vm-private\n' \
  269. b'rw=True\n' \
  270. b'size=2\n'
  271. self.app.expected_calls[
  272. ('new-vm', 'admin.vm.volume.Info', 'volatile', None)] = \
  273. b'0\x00' \
  274. b'snap_on_start=False\n' \
  275. b'save_on_stop=False\n' \
  276. b'pool=other-pool\n' \
  277. b'vid=new-vm-volatile\n' \
  278. b'rw=True\n' \
  279. b'size=2\n'
  280. self.app.expected_calls[
  281. ('new-vm', 'admin.vm.volume.Info', 'kernel', None)] = \
  282. b'0\x00' \
  283. b'snap_on_start=False\n' \
  284. b'save_on_stop=False\n' \
  285. b'pool=linux-kernel\n' \
  286. b'vid=kernel-version\n' \
  287. b'rw=False\n' \
  288. b'size=2\n'
  289. self.app.expected_calls[
  290. ('template', 'admin.vm.volume.CloneFrom', 'root', None)] = \
  291. b'0\0clone-cookie'
  292. self.app.expected_calls[
  293. ('new-vm', 'admin.vm.volume.CloneTo', 'root', b'clone-cookie')] = \
  294. b'0\0'
  295. qubesadmin.tools.qvm_create.main(['-C', 'StandaloneVM',
  296. '-t', 'template', '-l', 'red', 'new-vm'],
  297. app=self.app)
  298. self.assertAllCalled()
  299. def test_012_invalid_label(self):
  300. self.app.expected_calls[('dom0', 'admin.label.List', None, None)] = \
  301. b'0\x00red\nblue\n'
  302. with self.assertRaises(SystemExit):
  303. with qubesadmin.tests.tools.StderrBuffer() as stderr:
  304. qubesadmin.tools.qvm_create.main(['-l', 'invalid', 'name'],
  305. app=self.app)
  306. self.assertIn('red, blue', stderr.getvalue())
  307. self.assertAllCalled()