storage.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  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 subprocess
  21. import qubesadmin.tests
  22. import qubesadmin.storage
  23. class TestVMVolume(qubesadmin.tests.QubesTestCase):
  24. def setUp(self):
  25. super(TestVMVolume, self).setUp()
  26. self.vol = qubesadmin.storage.Volume(self.app, vm='test-vm',
  27. vm_name='volname')
  28. self.pool_vol = qubesadmin.storage.Volume(self.app, pool='test-pool',
  29. vid='some-id')
  30. def expect_info(self):
  31. self.app.expected_calls[
  32. ('test-vm', 'admin.vm.volume.Info', 'volname', None)] = \
  33. b'0\x00' \
  34. b'pool=test-pool\n' \
  35. b'vid=some-id\n' \
  36. b'size=1024\n' \
  37. b'usage=512\n' \
  38. b'rw=True\n' \
  39. b'snap_on_start=True\n' \
  40. b'save_on_stop=True\n' \
  41. b'source=\n' \
  42. b'internal=True\n' \
  43. b'revisions_to_keep=3\n'
  44. def test_000_qubesd_call(self):
  45. self.app.expected_calls[
  46. ('test-vm', 'admin.vm.volume.TestMethod', 'volname', None)] = \
  47. b'0\x00method_result'
  48. self.assertEqual(self.vol._qubesd_call('TestMethod'),
  49. b'method_result')
  50. self.assertAllCalled()
  51. def test_001_fetch_info(self):
  52. self.app.expected_calls[
  53. ('test-vm', 'admin.vm.volume.Info', 'volname', None)] = \
  54. b'0\x00prop1=val1\nprop2=val2\n'
  55. self.vol._fetch_info()
  56. self.assertEqual(self.vol._info, {'prop1': 'val1', 'prop2': 'val2'})
  57. self.assertAllCalled()
  58. def test_010_pool(self):
  59. self.expect_info()
  60. self.assertEqual(self.vol.pool, 'test-pool')
  61. self.assertAllCalled()
  62. def test_011_vid(self):
  63. self.expect_info()
  64. self.assertEqual(self.vol.vid, 'some-id')
  65. self.assertAllCalled()
  66. def test_012_size(self):
  67. self.expect_info()
  68. self.assertEqual(self.vol.size, 1024)
  69. self.assertAllCalled()
  70. def test_013_usage(self):
  71. self.expect_info()
  72. self.assertEqual(self.vol.usage, 512)
  73. self.assertAllCalled()
  74. def test_014_rw(self):
  75. self.expect_info()
  76. self.assertEqual(self.vol.rw, True)
  77. self.assertAllCalled()
  78. def test_015_snap_on_start(self):
  79. self.expect_info()
  80. self.assertEqual(self.vol.snap_on_start, True)
  81. self.assertAllCalled()
  82. def test_016_save_on_stop(self):
  83. self.expect_info()
  84. self.assertEqual(self.vol.save_on_stop, True)
  85. self.assertAllCalled()
  86. def test_017_source_none(self):
  87. self.expect_info()
  88. self.assertEqual(self.vol.source, None)
  89. self.assertAllCalled()
  90. def test_018_source(self):
  91. self.expect_info()
  92. call_key = list(self.app.expected_calls)[0]
  93. self.app.expected_calls[call_key] = self.app.expected_calls[
  94. call_key].replace(b'source=\n', b'source=test-pool:other-id\n')
  95. self.assertEqual(self.vol.source, 'test-pool:other-id')
  96. self.assertAllCalled()
  97. def test_019_internal(self):
  98. self.expect_info()
  99. self.assertEqual(self.vol.internal, True)
  100. self.assertAllCalled()
  101. def test_020_revisions_to_keep(self):
  102. self.expect_info()
  103. self.assertEqual(self.vol.revisions_to_keep, 3)
  104. self.assertAllCalled()
  105. def test_021_revisions(self):
  106. self.app.expected_calls[
  107. ('test-vm', 'admin.vm.volume.ListSnapshots', 'volname', None)] = \
  108. b'0\x00' \
  109. b'snapid1\n' \
  110. b'snapid2\n' \
  111. b'snapid3\n'
  112. self.assertEqual(self.vol.revisions,
  113. ['snapid1', 'snapid2', 'snapid3'])
  114. self.assertAllCalled()
  115. def test_022_revisions_empty(self):
  116. self.app.expected_calls[
  117. ('test-vm', 'admin.vm.volume.ListSnapshots', 'volname', None)] = \
  118. b'0\x00'
  119. self.assertEqual(self.vol.revisions, [])
  120. self.assertAllCalled()
  121. def test_030_resize(self):
  122. self.app.expected_calls[
  123. ('test-vm', 'admin.vm.volume.Resize', 'volname', b'2048')] = b'0\x00'
  124. self.vol.resize(2048)
  125. self.assertAllCalled()
  126. def test_031_revert(self):
  127. self.app.expected_calls[
  128. ('test-vm', 'admin.vm.volume.Revert', 'volname', b'snapid1')] = \
  129. b'0\x00'
  130. self.vol.revert('snapid1')
  131. self.assertAllCalled()
  132. def test_040_import_data(self):
  133. self.app.expected_calls[
  134. ('test-vm', 'admin.vm.volume.Import', 'volname', b'some-data')] = \
  135. b'0\x00'
  136. input_proc = subprocess.Popen(['echo', '-n', 'some-data'],
  137. stdout=subprocess.PIPE)
  138. self.vol.import_data(input_proc.stdout)
  139. input_proc.stdout.close()
  140. self.assertAllCalled()
  141. def test_050_clone(self):
  142. self.app.expected_calls[
  143. ('source-vm', 'admin.vm.volume.CloneFrom', 'volname', None)] = \
  144. b'0\x00abcdef'
  145. self.app.expected_calls[
  146. ('test-vm', 'admin.vm.volume.CloneTo', 'volname', b'abcdef')] = \
  147. b'0\x00'
  148. self.app.expected_calls[
  149. ('dom0', 'admin.vm.List', None, None)] = \
  150. b'0\x00source-vm class=AppVM state=Halted\n'
  151. self.app.expected_calls[
  152. ('source-vm', 'admin.vm.volume.List', None, None)] = \
  153. b'0\x00volname\nother\n'
  154. self.vol.clone(self.app.domains['source-vm'].volumes['volname'])
  155. self.assertAllCalled()
  156. class TestPoolVolume(TestVMVolume):
  157. def setUp(self):
  158. super(TestPoolVolume, self).setUp()
  159. self.vol = qubesadmin.storage.Volume(self.app, pool='test-pool',
  160. vid='some-id')
  161. def test_000_qubesd_call(self):
  162. self.app.expected_calls[
  163. ('dom0', 'admin.pool.volume.TestMethod',
  164. 'test-pool', b'some-id')] = \
  165. b'0\x00method_result'
  166. self.assertEqual(self.vol._qubesd_call('TestMethod'),
  167. b'method_result')
  168. self.assertAllCalled()
  169. def expect_info(self):
  170. self.app.expected_calls[
  171. ('dom0', 'admin.pool.volume.Info', 'test-pool', b'some-id')] = \
  172. b'0\x00' \
  173. b'pool=test-pool\n' \
  174. b'vid=some-id\n' \
  175. b'size=1024\n' \
  176. b'usage=512\n' \
  177. b'rw=True\n' \
  178. b'snap_on_start=True\n' \
  179. b'save_on_stop=True\n' \
  180. b'source=\n' \
  181. b'internal=True\n' \
  182. b'revisions_to_keep=3\n'
  183. def test_001_fetch_info(self):
  184. self.app.expected_calls[
  185. ('dom0', 'admin.pool.volume.Info', 'test-pool',
  186. b'some-id')] = \
  187. b'0\x00prop1=val1\nprop2=val2\n'
  188. self.vol._fetch_info()
  189. self.assertEqual(self.vol._info, {'prop1': 'val1', 'prop2': 'val2'})
  190. self.assertAllCalled()
  191. def test_010_pool(self):
  192. # this should _not_ produce any api call, as pool is already known
  193. self.assertEqual(self.vol.pool, 'test-pool')
  194. self.assertAllCalled()
  195. def test_011_vid(self):
  196. # this should _not_ produce any api call, as vid is already known
  197. self.assertEqual(self.vol.vid, 'some-id')
  198. self.assertAllCalled()
  199. def test_021_revisions(self):
  200. self.app.expected_calls[
  201. ('dom0', 'admin.pool.volume.ListSnapshots',
  202. 'test-pool', b'some-id')] = \
  203. b'0\x00' \
  204. b'snapid1\n' \
  205. b'snapid2\n' \
  206. b'snapid3\n'
  207. self.assertEqual(self.vol.revisions,
  208. ['snapid1', 'snapid2', 'snapid3'])
  209. self.assertAllCalled()
  210. def test_022_revisions_empty(self):
  211. self.app.expected_calls[
  212. ('dom0', 'admin.pool.volume.ListSnapshots',
  213. 'test-pool', b'some-id')] = b'0\x00'
  214. self.assertEqual(self.vol.revisions, [])
  215. self.assertAllCalled()
  216. def test_030_resize(self):
  217. self.app.expected_calls[
  218. ('dom0', 'admin.pool.volume.Resize',
  219. 'test-pool', b'some-id 2048')] = b'0\x00'
  220. self.vol.resize(2048)
  221. self.assertAllCalled()
  222. def test_031_revert(self):
  223. self.app.expected_calls[
  224. ('dom0', 'admin.pool.volume.Revert', 'test-pool',
  225. b'some-id snapid1')] = b'0\x00'
  226. self.vol.revert('snapid1')
  227. self.assertAllCalled()
  228. def test_040_import_data(self):
  229. self.skipTest('admin.pool.volume.Import not supported')
  230. def test_050_clone(self):
  231. self.app.expected_calls[
  232. ('dom0', 'admin.pool.volume.CloneFrom', 'test-pool',
  233. b'volid')] = b'0\x00abcdef'
  234. self.app.expected_calls[
  235. ('dom0', 'admin.pool.volume.CloneTo', 'test-pool',
  236. b'some-id abcdef')] = b'0\x00'
  237. source_vol = qubesadmin.storage.Volume(self.app, pool='test-pool',
  238. vid='volid')
  239. self.vol.clone(source_vol)
  240. self.assertAllCalled()
  241. def test_050_clone_wrong_volume(self):
  242. self.skipTest('admin.pool.volume.Clone not supported')
  243. class TestPool(qubesadmin.tests.QubesTestCase):
  244. def test_000_list(self):
  245. self.app.expected_calls[('dom0', 'admin.pool.List', None, None)] = \
  246. b'0\x00file\nlvm\n'
  247. seen = set()
  248. for pool in self.app.pools:
  249. self.assertIsInstance(pool, qubesadmin.storage.Pool)
  250. self.assertIn(pool.name, ('file', 'lvm'))
  251. self.assertNotIn(pool.name, seen)
  252. seen.add(pool.name)
  253. self.assertEqual(seen, set(['file', 'lvm']))
  254. self.assertAllCalled()
  255. def test_010_config(self):
  256. self.app.expected_calls[('dom0', 'admin.pool.List', None, None)] = \
  257. b'0\x00file\nlvm\n'
  258. self.app.expected_calls[('dom0', 'admin.pool.Info', 'file', None)] = \
  259. b'0\x00driver=file\n' \
  260. b'dir_path=/var/lib/qubes\n' \
  261. b'name=file\n' \
  262. b'revisions_to_keep=3\n'
  263. pool = self.app.pools['file']
  264. self.assertEqual(pool.config, {
  265. 'driver': 'file',
  266. 'dir_path': '/var/lib/qubes',
  267. 'name': 'file',
  268. 'revisions_to_keep': '3',
  269. })
  270. self.assertAllCalled()
  271. def test_020_volumes(self):
  272. self.app.expected_calls[('dom0', 'admin.pool.List', None, None)] = \
  273. b'0\x00file\nlvm\n'
  274. self.app.expected_calls[
  275. ('dom0', 'admin.pool.volume.List', 'file', None)] = \
  276. b'0\x00vol1\n' \
  277. b'vol2\n'
  278. pool = self.app.pools['file']
  279. seen = set()
  280. for volume in pool.volumes:
  281. self.assertIsInstance(volume, qubesadmin.storage.Volume)
  282. self.assertIn(volume.vid, ('vol1', 'vol2'))
  283. self.assertEqual(volume.pool, 'file')
  284. self.assertNotIn(volume.vid, seen)
  285. seen.add(volume.vid)
  286. self.assertEqual(seen, set(['vol1', 'vol2']))
  287. self.assertAllCalled()
  288. def test_030_pool_drivers(self):
  289. self.app.expected_calls[
  290. ('dom0', 'admin.pool.ListDrivers', None, None)] = \
  291. b'0\x00file dir_path revisions_to_keep\n' \
  292. b'lvm volume_group thin_pool revisions_to_keep\n'
  293. self.assertEqual(set(self.app.pool_drivers), set(['file', 'lvm']))
  294. self.assertEqual(set(self.app.pool_driver_parameters('file')),
  295. set(['dir_path', 'revisions_to_keep']))
  296. self.assertAllCalled()
  297. def test_040_add(self):
  298. self.app.expected_calls[
  299. ('dom0', 'admin.pool.Add', 'some-driver',
  300. b'name=test-pool\nparam1=value1\nparam2=123\n')] = b'0\x00'
  301. self.app.add_pool('test-pool', driver='some-driver',
  302. param1='value1', param2=123)
  303. self.assertAllCalled()
  304. def test_050_remove(self):
  305. self.app.expected_calls[
  306. ('dom0', 'admin.pool.Remove', 'test-pool', None)] = b'0\x00'
  307. self.app.remove_pool('test-pool')
  308. self.assertAllCalled()