storage_file.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. # The Qubes OS Project, https://www.qubes-os.org/
  2. #
  3. # Copyright (C) 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License along
  16. # with this program; if not, write to the Free Software Foundation, Inc.,
  17. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. import os
  19. import shutil
  20. import qubes.storage
  21. import qubes.tests.storage
  22. from qubes.config import defaults
  23. from qubes.storage import Storage
  24. from qubes.storage.file import (OriginFile, ReadOnlyFile, ReadWriteFile,
  25. SnapshotFile, VolatileFile)
  26. from qubes.tests import QubesTestCase, SystemTestsMixin
  27. from qubes.tests.storage import TestVM
  28. # :pylint: disable=invalid-name
  29. class TestApp(qubes.Qubes):
  30. def __init__(self, *args, **kwargs):
  31. super(TestApp, self).__init__('/tmp/qubes-test.xml',
  32. load=False, offline_mode=True, **kwargs)
  33. self.load_initial_values()
  34. self.pools['linux-kernel'].dir_path = '/tmp/qubes-test-kernel'
  35. dummy_kernel = os.path.join(
  36. self.pools['linux-kernel'].dir_path, 'dummy')
  37. os.makedirs(dummy_kernel)
  38. open(os.path.join(dummy_kernel, 'vmlinuz'), 'w').close()
  39. open(os.path.join(dummy_kernel, 'modules.img'), 'w').close()
  40. open(os.path.join(dummy_kernel, 'initramfs'), 'w').close()
  41. self.default_kernel = 'dummy'
  42. def cleanup(self):
  43. shutil.rmtree(self.pools['linux-kernel'].dir_path)
  44. def create_dummy_template(self):
  45. self.add_new_vm(qubes.vm.templatevm.TemplateVM,
  46. name='test-template', label='red',
  47. memory=1024, maxmem=1024)
  48. self.default_template = 'test-template'
  49. class TC_00_FilePool(QubesTestCase):
  50. """ This class tests some properties of the 'default' pool. """
  51. def setUp(self):
  52. super(TC_00_FilePool, self).setUp()
  53. self.app = TestApp()
  54. def tearDown(self):
  55. self.app.cleanup()
  56. super(TC_00_FilePool, self).tearDown()
  57. def test000_default_pool_dir(self):
  58. """ The predefined dir for the default pool should be ``/var/lib/qubes``
  59. .. sealso::
  60. Data :data:``qubes.qubes.defaults['pool_config']``.
  61. """
  62. result = self.app.get_pool("default").dir_path
  63. expected = '/var/lib/qubes'
  64. self.assertEquals(result, expected)
  65. def test001_default_storage_class(self):
  66. """ Check when using default pool the Storage is ``Storage``. """
  67. result = self._init_app_vm().storage
  68. self.assertIsInstance(result, Storage)
  69. def _init_app_vm(self):
  70. """ Return initalised, but not created, AppVm. """
  71. vmname = self.make_vm_name('appvm')
  72. self.app.create_dummy_template()
  73. return self.app.add_new_vm(qubes.vm.appvm.AppVM,
  74. name=vmname,
  75. template=self.app.default_template,
  76. label='red')
  77. class TC_01_FileVolumes(QubesTestCase):
  78. POOL_DIR = '/tmp/test-pool'
  79. POOL_NAME = 'test-pool'
  80. POOL_CONF = {'driver': 'file', 'dir_path': POOL_DIR, 'name': POOL_NAME}
  81. def setUp(self):
  82. """ Add a test file based storage pool """
  83. super(TC_01_FileVolumes, self).setUp()
  84. self.app = TestApp()
  85. self.app.create_dummy_template()
  86. self.app.add_pool(**self.POOL_CONF)
  87. def tearDown(self):
  88. """ Remove the file based storage pool after testing """
  89. self.app.remove_pool("test-pool")
  90. self.app.cleanup()
  91. super(TC_01_FileVolumes, self).tearDown()
  92. shutil.rmtree(self.POOL_DIR, ignore_errors=True)
  93. def test_000_origin_volume(self):
  94. config = {
  95. 'name': 'root',
  96. 'pool': self.POOL_NAME,
  97. 'volume_type': 'origin',
  98. 'size': defaults['root_img_size'],
  99. }
  100. vm = TestVM(self)
  101. result = self.app.get_pool(self.POOL_NAME).init_volume(vm, config)
  102. self.assertIsInstance(result, OriginFile)
  103. self.assertEqual(result.name, 'root')
  104. self.assertEqual(result.pool, self.POOL_NAME)
  105. self.assertEqual(result.size, defaults['root_img_size'])
  106. def test_001_snapshot_volume(self):
  107. original_path = '/var/lib/qubes/vm-templates/fedora-23/root.img'
  108. original_size = qubes.config.defaults['root_img_size']
  109. config = {
  110. 'name': 'root',
  111. 'pool': 'default',
  112. 'volume_type': 'snapshot',
  113. 'vid': original_path,
  114. }
  115. vm = TestVM(self, template=self.app.default_template)
  116. result = self.app.get_pool(self.POOL_NAME).init_volume(vm, config)
  117. self.assertIsInstance(result, SnapshotFile)
  118. self.assertEqual(result.name, 'root')
  119. self.assertEqual(result.pool, 'default')
  120. self.assertEqual(result.size, original_size)
  121. def test_002_read_write_volume(self):
  122. config = {
  123. 'name': 'root',
  124. 'pool': self.POOL_NAME,
  125. 'volume_type': 'read-write',
  126. 'size': defaults['root_img_size'],
  127. }
  128. vm = TestVM(self)
  129. result = self.app.get_pool(self.POOL_NAME).init_volume(vm, config)
  130. self.assertIsInstance(result, ReadWriteFile)
  131. self.assertEqual(result.name, 'root')
  132. self.assertEqual(result.pool, self.POOL_NAME)
  133. self.assertEqual(result.size, defaults['root_img_size'])
  134. def test_003_read_volume(self):
  135. template = self.app.default_template
  136. original_path = template.volumes['root'].vid
  137. original_size = qubes.config.defaults['root_img_size']
  138. config = {
  139. 'name': 'root',
  140. 'pool': 'default',
  141. 'volume_type': 'read-only',
  142. 'vid': original_path
  143. }
  144. vm = TestVM(self, template=template)
  145. result = self.app.get_pool(self.POOL_NAME).init_volume(vm, config)
  146. self.assertIsInstance(result, ReadOnlyFile)
  147. self.assertEqual(result.name, 'root')
  148. self.assertEqual(result.pool, 'default')
  149. self.assertEqual(result.size, original_size)
  150. def test_004_volatile_volume(self):
  151. config = {
  152. 'name': 'root',
  153. 'pool': self.POOL_NAME,
  154. 'volume_type': 'volatile',
  155. 'size': defaults['root_img_size'],
  156. }
  157. vm = TestVM(self)
  158. result = self.app.get_pool(self.POOL_NAME).init_volume(vm, config)
  159. self.assertIsInstance(result, VolatileFile)
  160. self.assertEqual(result.name, 'root')
  161. self.assertEqual(result.pool, self.POOL_NAME)
  162. self.assertEqual(result.size, defaults['root_img_size'])
  163. def test_005_appvm_volumes(self):
  164. ''' Check if AppVM volumes are propertly initialized '''
  165. vmname = self.make_vm_name('appvm')
  166. vm = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  167. name=vmname,
  168. template=self.app.default_template,
  169. label='red')
  170. volumes = vm.volumes
  171. self.assertIsInstance(volumes['root'], SnapshotFile)
  172. self.assertIsInstance(volumes['private'], OriginFile)
  173. self.assertIsInstance(volumes['volatile'], VolatileFile)
  174. expected = vm.template.dir_path + '/root.img:' + vm.template.dir_path \
  175. + '/root-cow.img'
  176. self.assertVolumePath(vm, 'root', expected, rw=False)
  177. expected = vm.dir_path + '/private.img:' + \
  178. vm.dir_path + '/private-cow.img'
  179. self.assertVolumePath(vm, 'private', expected, rw=True)
  180. expected = vm.dir_path + '/volatile.img'
  181. self.assertVolumePath(vm, 'volatile', expected, rw=True)
  182. def test_006_template_volumes(self):
  183. ''' Check if TemplateVM volumes are propertly initialized '''
  184. vmname = self.make_vm_name('appvm')
  185. vm = self.app.add_new_vm(qubes.vm.templatevm.TemplateVM,
  186. name=vmname,
  187. label='red')
  188. volumes = vm.volumes
  189. self.assertIsInstance(volumes['root'], OriginFile)
  190. self.assertIsInstance(volumes['private'], ReadWriteFile)
  191. self.assertIsInstance(volumes['volatile'], VolatileFile)
  192. expected = vm.dir_path + '/root.img:' + vm.dir_path + '/root-cow.img'
  193. self.assertVolumePath(vm, 'root', expected, rw=True)
  194. expected = vm.dir_path + '/private.img'
  195. self.assertVolumePath(vm, 'private', expected, rw=True)
  196. expected = vm.dir_path + '/volatile.img'
  197. self.assertVolumePath(vm, 'volatile', expected, rw=True)
  198. def assertVolumePath(self, vm, dev_name, expected, rw=True):
  199. # :pylint: disable=invalid-name
  200. volumes = vm.volumes
  201. b_dev = volumes[dev_name].block_device()
  202. self.assertEqual(b_dev.rw, rw)
  203. self.assertEquals(b_dev.path, expected)
  204. class TC_03_FilePool(QubesTestCase):
  205. """ Test the paths for the default file based pool (``FilePool``).
  206. """
  207. POOL_DIR = '/tmp/test-pool'
  208. APPVMS_DIR = '/tmp/test-pool/appvms'
  209. TEMPLATES_DIR = '/tmp/test-pool/vm-templates'
  210. SERVICE_DIR = '/tmp/test-pool/servicevms'
  211. POOL_NAME = 'test-pool'
  212. POOL_CONFIG = {'driver': 'file', 'dir_path': POOL_DIR, 'name': POOL_NAME}
  213. def setUp(self):
  214. """ Add a test file based storage pool """
  215. super(TC_03_FilePool, self).setUp()
  216. self._orig_qubes_base_dir = qubes.config.system_path['qubes_base_dir']
  217. qubes.config.system_path['qubes_base_dir'] = '/tmp/qubes-test'
  218. self.app = TestApp()
  219. self.app.create_dummy_template()
  220. self.app.add_pool(**self.POOL_CONFIG)
  221. def tearDown(self):
  222. """ Remove the file based storage pool after testing """
  223. self.app.remove_pool("test-pool")
  224. self.app.cleanup()
  225. super(TC_03_FilePool, self).tearDown()
  226. shutil.rmtree(self.POOL_DIR, ignore_errors=True)
  227. if os.path.exists('/tmp/qubes-test'):
  228. shutil.rmtree('/tmp/qubes-test')
  229. qubes.config.system_path['qubes_base_dir'] = self._orig_qubes_base_dir
  230. def test_001_pool_exists(self):
  231. """ Check if the storage pool was added to the storage pool config """
  232. self.assertIn('test-pool', self.app.pools.keys())
  233. def test_002_pool_dir_create(self):
  234. """ Check if the storage pool dir and subdirs were created """
  235. # The dir should not exists before
  236. pool_name = 'foo'
  237. pool_dir = '/tmp/foo'
  238. appvms_dir = '/tmp/foo/appvms'
  239. templates_dir = '/tmp/foo/vm-templates'
  240. self.assertFalse(os.path.exists(pool_dir))
  241. self.app.add_pool(name=pool_name, dir_path=pool_dir, driver='file')
  242. self.assertTrue(os.path.exists(pool_dir))
  243. self.assertTrue(os.path.exists(appvms_dir))
  244. self.assertTrue(os.path.exists(templates_dir))
  245. shutil.rmtree(pool_dir, ignore_errors=True)
  246. def test_011_appvm_file_images(self):
  247. """ Check if all the needed image files are created for an AppVm"""
  248. vmname = self.make_vm_name('appvm')
  249. vm = self.app.add_new_vm(qubes.vm.appvm.AppVM,
  250. name=vmname,
  251. template=self.app.default_template,
  252. volume_config={
  253. 'private': {
  254. 'pool': 'test-pool'
  255. },
  256. 'volatile': {
  257. 'pool': 'test-pool'
  258. }
  259. },
  260. label='red')
  261. vm.storage.create_on_disk()
  262. expected_vmdir = os.path.join(self.APPVMS_DIR, vm.name)
  263. expected_private_origin_path = \
  264. os.path.join(expected_vmdir, 'private.img')
  265. expected_private_cow_path = \
  266. os.path.join(expected_vmdir, 'private-cow.img')
  267. expected_private_path = '%s:%s' % (expected_private_origin_path,
  268. expected_private_cow_path)
  269. self.assertEquals(vm.volumes['private'].path, expected_private_path)
  270. self.assertEqualsAndExists(vm.volumes['private'].path_origin,
  271. expected_private_origin_path)
  272. self.assertEqualsAndExists(vm.volumes['private'].path_cow,
  273. expected_private_cow_path)
  274. expected_volatile_path = os.path.join(expected_vmdir, 'volatile.img')
  275. self.assertEqualsAndExists(vm.volumes['volatile'].path,
  276. expected_volatile_path)
  277. def test_013_template_file_images(self):
  278. """ Check if root.img, private.img, volatile.img and root-cow.img are
  279. created propertly by the storage system
  280. """
  281. vmname = self.make_vm_name('tmvm')
  282. vm = self.app.add_new_vm(qubes.vm.templatevm.TemplateVM,
  283. name=vmname,
  284. volume_config={
  285. 'root': {
  286. 'pool': 'test-pool'
  287. },
  288. 'private': {
  289. 'pool': 'test-pool'
  290. },
  291. 'volatile': {
  292. 'pool': 'test-pool'
  293. }
  294. },
  295. label='red')
  296. vm.create_on_disk()
  297. expected_vmdir = os.path.join(self.TEMPLATES_DIR, vm.name)
  298. expected_root_origin_path = os.path.join(expected_vmdir, 'root.img')
  299. expected_root_cow_path = os.path.join(expected_vmdir, 'root-cow.img')
  300. expected_root_path = '%s:%s' % (expected_root_origin_path,
  301. expected_root_cow_path)
  302. self.assertEquals(vm.volumes['root'].path, expected_root_path)
  303. self.assertExist(vm.volumes['root'].path_origin)
  304. expected_private_path = os.path.join(expected_vmdir, 'private.img')
  305. self.assertEqualsAndExists(vm.volumes['private'].path,
  306. expected_private_path)
  307. expected_volatile_path = os.path.join(expected_vmdir, 'volatile.img')
  308. self.assertEqualsAndExists(vm.volumes['volatile'].path,
  309. expected_volatile_path)
  310. vm.storage.commit_template_changes()
  311. expected_rootcow_path = os.path.join(expected_vmdir, 'root-cow.img')
  312. self.assertEqualsAndExists(vm.volumes['root'].path_cow,
  313. expected_rootcow_path)
  314. def assertEqualsAndExists(self, result_path, expected_path):
  315. """ Check if the ``result_path``, matches ``expected_path`` and exists.
  316. See also: :meth:``assertExist``
  317. """
  318. # :pylint: disable=invalid-name
  319. self.assertEquals(result_path, expected_path)
  320. self.assertExist(result_path)
  321. def assertExist(self, path):
  322. """ Assert that the given path exists. """
  323. # :pylint: disable=invalid-name
  324. self.assertTrue(os.path.exists(path), "Path %s does not exist" % path)