storage_lvm.py 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044
  1. #
  2. # The Qubes OS Project, http://www.qubes-os.org
  3. #
  4. # Copyright (C) 2016 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
  5. #
  6. # This library is free software; you can redistribute it and/or
  7. # modify it under the terms of the GNU Lesser General Public
  8. # License as published by the Free Software Foundation; either
  9. # version 2.1 of the License, or (at your option) any later version.
  10. #
  11. # This library is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. # Lesser General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Lesser General Public
  17. # License along with this library; if not, see <https://www.gnu.org/licenses/>.
  18. #
  19. ''' Tests for lvm storage driver. By default tests are going to use the
  20. 'qubes_dom0/pool00'. An alternative LVM thin pool may be provided via
  21. :envvar:`DEFAULT_LVM_POOL` shell variable.
  22. Any pool variables prefixed with 'LVM_' or 'lvm_' represent a LVM
  23. 'volume_group/thin_pool' combination. Pool variables without a prefix
  24. represent a :py:class:`qubes.storage.lvm.ThinPool`.
  25. '''
  26. import os
  27. import subprocess
  28. import tempfile
  29. import unittest
  30. import unittest.mock
  31. import qubes.tests
  32. import qubes.storage
  33. from qubes.storage.lvm import ThinPool, ThinVolume, qubes_lvm
  34. if 'DEFAULT_LVM_POOL' in os.environ.keys():
  35. DEFAULT_LVM_POOL = os.environ['DEFAULT_LVM_POOL']
  36. else:
  37. DEFAULT_LVM_POOL = 'qubes_dom0/pool00'
  38. def lvm_pool_exists(volume_group, thin_pool):
  39. ''' Returns ``True`` if thin pool exists in the volume group. '''
  40. path = "/dev/mapper/{!s}-{!s}".format(
  41. volume_group.replace('-', '--'),
  42. thin_pool.replace('-', '--'))
  43. return os.path.exists(path)
  44. def skipUnlessLvmPoolExists(test_item): # pylint: disable=invalid-name
  45. ''' Decorator that skips LVM tests if the default pool is missing. '''
  46. volume_group, thin_pool = DEFAULT_LVM_POOL.split('/', 1)
  47. result = lvm_pool_exists(volume_group, thin_pool)
  48. msg = 'LVM thin pool {!r} does not exist'.format(DEFAULT_LVM_POOL)
  49. return unittest.skipUnless(result, msg)(test_item)
  50. POOL_CONF = {'name': 'test-lvm',
  51. 'driver': 'lvm_thin',
  52. 'volume_group': DEFAULT_LVM_POOL.split('/')[0],
  53. 'thin_pool': DEFAULT_LVM_POOL.split('/')[1]}
  54. class ThinPoolBase(qubes.tests.QubesTestCase):
  55. ''' Sanity tests for :py:class:`qubes.storage.lvm.ThinPool` '''
  56. created_pool = False
  57. def setUp(self):
  58. super(ThinPoolBase, self).setUp()
  59. volume_group, thin_pool = DEFAULT_LVM_POOL.split('/', 1)
  60. self.pool = self._find_pool(volume_group, thin_pool)
  61. if not self.pool:
  62. self.pool = self.app.add_pool(**POOL_CONF)
  63. self.created_pool = True
  64. def tearDown(self):
  65. ''' Remove the default lvm pool if it was created only for this test '''
  66. if self.created_pool:
  67. self.app.remove_pool(self.pool.name)
  68. super(ThinPoolBase, self).tearDown()
  69. def _find_pool(self, volume_group, thin_pool):
  70. ''' Returns the pool matching the specified ``volume_group`` &
  71. ``thin_pool``, or None.
  72. '''
  73. pools = [p for p in self.app.pools.values()
  74. if issubclass(p.__class__, ThinPool)]
  75. for pool in pools:
  76. if pool.volume_group == volume_group \
  77. and pool.thin_pool == thin_pool:
  78. return pool
  79. return None
  80. @skipUnlessLvmPoolExists
  81. class TC_00_ThinPool(ThinPoolBase):
  82. ''' Sanity tests for :py:class:`qubes.storage.lvm.ThinPool` '''
  83. def setUp(self):
  84. xml_path = '/tmp/qubes-test.xml'
  85. self.app = qubes.Qubes.create_empty_store(store=xml_path,
  86. clockvm=None,
  87. updatevm=None,
  88. offline_mode=True,
  89. )
  90. os.environ['QUBES_XML_PATH'] = xml_path
  91. super(TC_00_ThinPool, self).setUp()
  92. def tearDown(self):
  93. super(TC_00_ThinPool, self).tearDown()
  94. os.unlink(self.app.store)
  95. del self.app
  96. for attr in dir(self):
  97. if isinstance(getattr(self, attr), qubes.vm.BaseVM):
  98. delattr(self, attr)
  99. def test_000_default_thin_pool(self):
  100. ''' Check whether :py:data`DEFAULT_LVM_POOL` exists. This pool is
  101. created by default, if at installation time LVM + Thin was chosen.
  102. '''
  103. msg = 'Thin pool {!r} does not exist'.format(DEFAULT_LVM_POOL)
  104. self.assertTrue(self.pool, msg)
  105. def test_001_origin_volume(self):
  106. ''' Test origin volume creation '''
  107. config = {
  108. 'name': 'root',
  109. 'pool': self.pool.name,
  110. 'save_on_stop': True,
  111. 'rw': True,
  112. 'size': qubes.config.defaults['root_img_size'],
  113. }
  114. vm = qubes.tests.storage.TestVM(self)
  115. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  116. self.assertIsInstance(volume, ThinVolume)
  117. self.assertEqual(volume.name, 'root')
  118. self.assertEqual(volume.pool, self.pool.name)
  119. self.assertEqual(volume.size, qubes.config.defaults['root_img_size'])
  120. self.loop.run_until_complete(volume.create())
  121. path = "/dev/%s" % volume.vid
  122. self.assertTrue(os.path.exists(path), path)
  123. self.loop.run_until_complete(volume.remove())
  124. def test_003_read_write_volume(self):
  125. ''' Test read-write volume creation '''
  126. config = {
  127. 'name': 'root',
  128. 'pool': self.pool.name,
  129. 'rw': True,
  130. 'save_on_stop': True,
  131. 'size': qubes.config.defaults['root_img_size'],
  132. }
  133. vm = qubes.tests.storage.TestVM(self)
  134. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  135. self.assertIsInstance(volume, ThinVolume)
  136. self.assertEqual(volume.name, 'root')
  137. self.assertEqual(volume.pool, self.pool.name)
  138. self.assertEqual(volume.size, qubes.config.defaults['root_img_size'])
  139. self.loop.run_until_complete(volume.create())
  140. path = "/dev/%s" % volume.vid
  141. self.assertTrue(os.path.exists(path), path)
  142. self.loop.run_until_complete(volume.remove())
  143. def test_004_size(self):
  144. with self.assertNotRaises(NotImplementedError):
  145. size = self.pool.size
  146. environ = os.environ.copy()
  147. environ['LC_ALL'] = 'C.utf8'
  148. pool_size = subprocess.check_output(['sudo', 'lvs', '--noheadings',
  149. '-o', 'lv_size',
  150. '--units', 'b', self.pool.volume_group + '/' + self.pool.thin_pool],
  151. env=environ)
  152. self.assertEqual(size, int(pool_size.strip()[:-1]))
  153. def test_005_usage(self):
  154. with self.assertNotRaises(NotImplementedError):
  155. usage = self.pool.usage
  156. environ = os.environ.copy()
  157. environ['LC_ALL'] = 'C.utf8'
  158. pool_info = subprocess.check_output(['sudo', 'lvs', '--noheadings',
  159. '-o', 'lv_size,data_percent',
  160. '--units', 'b', self.pool.volume_group + '/' + self.pool.thin_pool],
  161. env=environ)
  162. pool_size, pool_usage = pool_info.strip().split()
  163. pool_size = int(pool_size[:-1])
  164. pool_usage = float(pool_usage)
  165. self.assertEqual(usage, int(pool_size * pool_usage / 100))
  166. def _get_size(self, path):
  167. if os.getuid() != 0:
  168. return int(
  169. subprocess.check_output(
  170. ['sudo', 'blockdev', '--getsize64', path]))
  171. fd = os.open(path, os.O_RDONLY)
  172. try:
  173. return os.lseek(fd, 0, os.SEEK_END)
  174. finally:
  175. os.close(fd)
  176. def test_006_resize(self):
  177. config = {
  178. 'name': 'root',
  179. 'pool': self.pool.name,
  180. 'rw': True,
  181. 'save_on_stop': True,
  182. 'size': 32 * 1024**2,
  183. }
  184. vm = qubes.tests.storage.TestVM(self)
  185. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  186. self.loop.run_until_complete(volume.create())
  187. self.addCleanup(self.loop.run_until_complete, volume.remove())
  188. path = "/dev/%s" % volume.vid
  189. new_size = 64 * 1024 ** 2
  190. self.loop.run_until_complete(volume.resize(new_size))
  191. self.assertEqual(self._get_size(path), new_size)
  192. self.assertEqual(volume.size, new_size)
  193. def test_007_resize_running(self):
  194. old_size = 32 * 1024**2
  195. config = {
  196. 'name': 'root',
  197. 'pool': self.pool.name,
  198. 'rw': True,
  199. 'save_on_stop': True,
  200. 'size': old_size,
  201. }
  202. vm = qubes.tests.storage.TestVM(self)
  203. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  204. self.loop.run_until_complete(volume.create())
  205. self.addCleanup(self.loop.run_until_complete, volume.remove())
  206. self.loop.run_until_complete(volume.start())
  207. path = "/dev/%s" % volume.vid
  208. path2 = "/dev/%s" % volume._vid_snap
  209. new_size = 64 * 1024 ** 2
  210. self.loop.run_until_complete(volume.resize(new_size))
  211. self.assertEqual(self._get_size(path), old_size)
  212. self.assertEqual(self._get_size(path2), new_size)
  213. self.assertEqual(volume.size, new_size)
  214. self.loop.run_until_complete(volume.stop())
  215. self.assertEqual(self._get_size(path), new_size)
  216. self.assertEqual(volume.size, new_size)
  217. def _get_lv_uuid(self, lv):
  218. sudo = [] if os.getuid() == 0 else ['sudo']
  219. lvs_output = subprocess.check_output(
  220. sudo + ['lvs', '--noheadings', '-o', 'lv_uuid', lv])
  221. return lvs_output.strip()
  222. def _get_lv_origin_uuid(self, lv):
  223. sudo = [] if os.getuid() == 0 else ['sudo']
  224. if qubes.storage.lvm.lvm_is_very_old:
  225. # no support for origin_uuid directly
  226. lvs_output = subprocess.check_output(
  227. sudo + ['lvs', '--noheadings', '-o', 'origin', lv])
  228. lvs_output = subprocess.check_output(
  229. sudo + ['lvs', '--noheadings', '-o', 'lv_uuid',
  230. lv.rsplit('/', 1)[0] + '/' + lvs_output.strip().decode()])
  231. else:
  232. lvs_output = subprocess.check_output(
  233. sudo + ['lvs', '--noheadings', '-o', 'origin_uuid', lv])
  234. return lvs_output.strip()
  235. def test_008_commit(self):
  236. ''' Test volume changes commit'''
  237. config = {
  238. 'name': 'root',
  239. 'pool': self.pool.name,
  240. 'save_on_stop': True,
  241. 'rw': True,
  242. 'size': qubes.config.defaults['root_img_size'],
  243. }
  244. vm = qubes.tests.storage.TestVM(self)
  245. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  246. self.loop.run_until_complete(volume.create())
  247. path_snap = '/dev/' + volume._vid_snap
  248. self.assertFalse(os.path.exists(path_snap), path_snap)
  249. origin_uuid = self._get_lv_uuid(volume.path)
  250. self.loop.run_until_complete(volume.start())
  251. snap_uuid = self._get_lv_uuid(path_snap)
  252. self.assertNotEqual(origin_uuid, snap_uuid)
  253. path = volume.path
  254. self.assertTrue(path.startswith('/dev/' + volume.vid),
  255. '{} does not start with /dev/{}'.format(path, volume.vid))
  256. self.assertTrue(os.path.exists(path), path)
  257. self.loop.run_until_complete(volume.remove())
  258. def test_009_interrupted_commit(self):
  259. ''' Test volume changes commit'''
  260. config = {
  261. 'name': 'root',
  262. 'pool': self.pool.name,
  263. 'save_on_stop': True,
  264. 'rw': True,
  265. 'revisions_to_keep': 2,
  266. 'size': qubes.config.defaults['root_img_size'],
  267. }
  268. vm = qubes.tests.storage.TestVM(self)
  269. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  270. # mock logging, to not interfere with time.time() mock
  271. volume.log = unittest.mock.Mock()
  272. # do not call volume.create(), do it manually to simulate
  273. # interrupted commit
  274. revisions = ['-1521065904-back', '-1521065905-back', '-snap']
  275. orig_uuids = {}
  276. for rev in revisions:
  277. cmd = ['create', self.pool._pool_id,
  278. volume.vid.split('/')[1] + rev, str(config['size'])]
  279. qubes_lvm(cmd)
  280. orig_uuids[rev] = self._get_lv_uuid(volume.vid + rev)
  281. qubes.storage.lvm.reset_cache()
  282. path_snap = '/dev/' + volume._vid_snap
  283. self.assertTrue(volume.is_dirty())
  284. self.assertEqual(volume.path,
  285. '/dev/' + volume.vid + revisions[1])
  286. expected_revisions = {
  287. revisions[0].lstrip('-'): '2018-03-14T22:18:24',
  288. revisions[1].lstrip('-'): '2018-03-14T22:18:25',
  289. }
  290. self.assertEqual(volume.revisions, expected_revisions)
  291. self.loop.run_until_complete(volume.start())
  292. self.assertEqual(volume.revisions, expected_revisions)
  293. snap_uuid = self._get_lv_uuid(path_snap)
  294. self.assertEqual(orig_uuids['-snap'], snap_uuid)
  295. self.assertTrue(volume.is_dirty())
  296. self.assertEqual(volume.path,
  297. '/dev/' + volume.vid + revisions[1])
  298. with unittest.mock.patch('time.time') as mock_time:
  299. mock_time.side_effect = [521065906]
  300. self.loop.run_until_complete(volume.stop())
  301. expected_revisions = {
  302. revisions[0].lstrip('-'): '2018-03-14T22:18:24',
  303. revisions[1].lstrip('-'): '2018-03-14T22:18:25',
  304. }
  305. self.assertFalse(volume.is_dirty())
  306. self.assertEqual(volume.revisions, expected_revisions)
  307. self.assertEqual(volume.path, '/dev/' + volume.vid)
  308. self.assertEqual(snap_uuid, self._get_lv_uuid(volume.path))
  309. self.assertFalse(os.path.exists(path_snap), path_snap)
  310. self.loop.run_until_complete(volume.remove())
  311. def test_010_migration1(self):
  312. '''Start with old revisions, then start interacting using new code'''
  313. config = {
  314. 'name': 'root',
  315. 'pool': self.pool.name,
  316. 'save_on_stop': True,
  317. 'rw': True,
  318. 'revisions_to_keep': 2,
  319. 'size': qubes.config.defaults['root_img_size'],
  320. }
  321. vm = qubes.tests.storage.TestVM(self)
  322. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  323. # mock logging, to not interfere with time.time() mock
  324. volume.log = unittest.mock.Mock()
  325. # do not call volume.create(), do it manually to have old LV naming
  326. revisions = ['', '-1521065904-back', '-1521065905-back']
  327. orig_uuids = {}
  328. for rev in revisions:
  329. cmd = ['create', self.pool._pool_id,
  330. volume.vid.split('/')[1] + rev, str(config['size'])]
  331. qubes_lvm(cmd)
  332. orig_uuids[rev] = self._get_lv_uuid(volume.vid + rev)
  333. qubes.storage.lvm.reset_cache()
  334. path_snap = '/dev/' + volume._vid_snap
  335. self.assertFalse(os.path.exists(path_snap), path_snap)
  336. expected_revisions = {
  337. revisions[1].lstrip('-'): '2018-03-14T22:18:24',
  338. revisions[2].lstrip('-'): '2018-03-14T22:18:25',
  339. }
  340. self.assertEqual(volume.revisions, expected_revisions)
  341. self.assertEqual(volume.path, '/dev/' + volume.vid)
  342. self.loop.run_until_complete(volume.start())
  343. snap_uuid = self._get_lv_uuid(path_snap)
  344. self.assertNotEqual(orig_uuids[''], snap_uuid)
  345. snap_origin_uuid = self._get_lv_origin_uuid(path_snap)
  346. self.assertEqual(orig_uuids[''], snap_origin_uuid)
  347. path = volume.path
  348. self.assertEqual(path, '/dev/' + volume.vid)
  349. self.assertTrue(os.path.exists(path), path)
  350. with unittest.mock.patch('time.time') as mock_time:
  351. mock_time.side_effect = ('1521065906', '1521065907')
  352. self.loop.run_until_complete(volume.stop())
  353. revisions.extend(['-1521065906-back'])
  354. expected_revisions = {
  355. revisions[2].lstrip('-'): '2018-03-14T22:18:25',
  356. revisions[3].lstrip('-'): '2018-03-14T22:18:26',
  357. }
  358. self.assertEqual(volume.revisions, expected_revisions)
  359. self.assertEqual(volume.path, '/dev/' + volume.vid)
  360. path_snap = '/dev/' + volume._vid_snap
  361. self.assertFalse(os.path.exists(path_snap), path_snap)
  362. self.assertTrue(os.path.exists('/dev/' + volume.vid))
  363. self.assertEqual(self._get_lv_uuid(volume.path), snap_uuid)
  364. prev_path = '/dev/' + volume.vid + revisions[3]
  365. self.assertEqual(self._get_lv_uuid(prev_path), orig_uuids[''])
  366. self.loop.run_until_complete(volume.remove())
  367. for rev in revisions:
  368. path = '/dev/' + volume.vid + rev
  369. self.assertFalse(os.path.exists(path), path)
  370. def test_011_migration2(self):
  371. '''VM started with old code, stopped with new'''
  372. config = {
  373. 'name': 'root',
  374. 'pool': self.pool.name,
  375. 'save_on_stop': True,
  376. 'rw': True,
  377. 'revisions_to_keep': 1,
  378. 'size': qubes.config.defaults['root_img_size'],
  379. }
  380. vm = qubes.tests.storage.TestVM(self)
  381. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  382. # mock logging, to not interfere with time.time() mock
  383. volume.log = unittest.mock.Mock()
  384. # do not call volume.create(), do it manually to have old LV naming
  385. revisions = ['', '-snap']
  386. orig_uuids = {}
  387. for rev in revisions:
  388. cmd = ['create', self.pool._pool_id,
  389. volume.vid.split('/')[1] + rev, str(config['size'])]
  390. qubes_lvm(cmd)
  391. orig_uuids[rev] = self._get_lv_uuid(volume.vid + rev)
  392. qubes.storage.lvm.reset_cache()
  393. path_snap = '/dev/' + volume._vid_snap
  394. self.assertTrue(os.path.exists(path_snap), path_snap)
  395. expected_revisions = {}
  396. self.assertEqual(volume.revisions, expected_revisions)
  397. self.assertEqual(volume.path, '/dev/' + volume.vid)
  398. self.assertTrue(volume.is_dirty())
  399. path = volume.path
  400. self.assertEqual(path, '/dev/' + volume.vid)
  401. self.assertTrue(os.path.exists(path), path)
  402. with unittest.mock.patch('time.time') as mock_time:
  403. mock_time.side_effect = ('1521065906', '1521065907')
  404. self.loop.run_until_complete(volume.stop())
  405. revisions.extend(['-1521065906-back'])
  406. expected_revisions = {
  407. revisions[2].lstrip('-'): '2018-03-14T22:18:26',
  408. }
  409. self.assertEqual(volume.revisions, expected_revisions)
  410. self.assertEqual(volume.path, '/dev/' + volume.vid)
  411. path_snap = '/dev/' + volume._vid_snap
  412. self.assertFalse(os.path.exists(path_snap), path_snap)
  413. self.assertTrue(os.path.exists('/dev/' + volume.vid))
  414. self.assertEqual(self._get_lv_uuid(volume.path), orig_uuids['-snap'])
  415. prev_path = '/dev/' + volume.vid + revisions[2]
  416. self.assertEqual(self._get_lv_uuid(prev_path), orig_uuids[''])
  417. self.loop.run_until_complete(volume.remove())
  418. for rev in revisions:
  419. path = '/dev/' + volume.vid + rev
  420. self.assertFalse(os.path.exists(path), path)
  421. def test_012_migration3(self):
  422. '''VM started with old code, started again with new, stopped with new'''
  423. config = {
  424. 'name': 'root',
  425. 'pool': self.pool.name,
  426. 'save_on_stop': True,
  427. 'rw': True,
  428. 'revisions_to_keep': 1,
  429. 'size': qubes.config.defaults['root_img_size'],
  430. }
  431. vm = qubes.tests.storage.TestVM(self)
  432. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  433. # mock logging, to not interfere with time.time() mock
  434. volume.log = unittest.mock.Mock()
  435. # do not call volume.create(), do it manually to have old LV naming
  436. revisions = ['', '-snap']
  437. orig_uuids = {}
  438. for rev in revisions:
  439. cmd = ['create', self.pool._pool_id,
  440. volume.vid.split('/')[1] + rev, str(config['size'])]
  441. qubes_lvm(cmd)
  442. orig_uuids[rev] = self._get_lv_uuid(volume.vid + rev)
  443. qubes.storage.lvm.reset_cache()
  444. path_snap = '/dev/' + volume._vid_snap
  445. self.assertTrue(os.path.exists(path_snap), path_snap)
  446. expected_revisions = {}
  447. self.assertEqual(volume.revisions, expected_revisions)
  448. self.assertTrue(volume.path, '/dev/' + volume.vid)
  449. self.assertTrue(volume.is_dirty())
  450. self.loop.run_until_complete(volume.start())
  451. self.assertEqual(volume.revisions, expected_revisions)
  452. self.assertEqual(volume.path, '/dev/' + volume.vid)
  453. # -snap LV should be unchanged
  454. self.assertEqual(self._get_lv_uuid(volume._vid_snap),
  455. orig_uuids['-snap'])
  456. self.loop.run_until_complete(volume.remove())
  457. for rev in revisions:
  458. path = '/dev/' + volume.vid + rev
  459. self.assertFalse(os.path.exists(path), path)
  460. def test_013_migration4(self):
  461. '''revisions_to_keep=0, VM started with old code, stopped with new'''
  462. config = {
  463. 'name': 'root',
  464. 'pool': self.pool.name,
  465. 'save_on_stop': True,
  466. 'rw': True,
  467. 'revisions_to_keep': 0,
  468. 'size': qubes.config.defaults['root_img_size'],
  469. }
  470. vm = qubes.tests.storage.TestVM(self)
  471. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  472. # mock logging, to not interfere with time.time() mock
  473. volume.log = unittest.mock.Mock()
  474. # do not call volume.create(), do it manually to have old LV naming
  475. revisions = ['', '-snap']
  476. orig_uuids = {}
  477. for rev in revisions:
  478. cmd = ['create', self.pool._pool_id,
  479. volume.vid.split('/')[1] + rev, str(config['size'])]
  480. qubes_lvm(cmd)
  481. orig_uuids[rev] = self._get_lv_uuid(volume.vid + rev)
  482. qubes.storage.lvm.reset_cache()
  483. path_snap = '/dev/' + volume._vid_snap
  484. self.assertTrue(os.path.exists(path_snap), path_snap)
  485. expected_revisions = {}
  486. self.assertEqual(volume.revisions, expected_revisions)
  487. self.assertEqual(volume.path, '/dev/' + volume.vid)
  488. self.assertTrue(volume.is_dirty())
  489. with unittest.mock.patch('time.time') as mock_time:
  490. mock_time.side_effect = ('1521065906', '1521065907')
  491. self.loop.run_until_complete(volume.stop())
  492. expected_revisions = {}
  493. self.assertEqual(volume.revisions, expected_revisions)
  494. self.assertEqual(volume.path, '/dev/' + volume.vid)
  495. self.loop.run_until_complete(volume.remove())
  496. for rev in revisions:
  497. path = '/dev/' + volume.vid + rev
  498. self.assertFalse(os.path.exists(path), path)
  499. def test_014_commit_keep_0(self):
  500. ''' Test volume changes commit, with revisions_to_keep=0'''
  501. config = {
  502. 'name': 'root',
  503. 'pool': self.pool.name,
  504. 'save_on_stop': True,
  505. 'rw': True,
  506. 'revisions_to_keep': 0,
  507. 'size': qubes.config.defaults['root_img_size'],
  508. }
  509. vm = qubes.tests.storage.TestVM(self)
  510. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  511. # mock logging, to not interfere with time.time() mock
  512. volume.log = unittest.mock.Mock()
  513. self.loop.run_until_complete(volume.create())
  514. self.assertFalse(volume.is_dirty())
  515. path = volume.path
  516. expected_revisions = {}
  517. self.assertEqual(volume.revisions, expected_revisions)
  518. self.loop.run_until_complete(volume.start())
  519. self.assertEqual(volume.revisions, expected_revisions)
  520. path_snap = '/dev/' + volume._vid_snap
  521. snap_uuid = self._get_lv_uuid(path_snap)
  522. self.assertTrue(volume.is_dirty())
  523. self.assertEqual(volume.path, path)
  524. with unittest.mock.patch('time.time') as mock_time:
  525. mock_time.side_effect = [521065906]
  526. self.loop.run_until_complete(volume.stop())
  527. self.assertFalse(volume.is_dirty())
  528. self.assertEqual(volume.revisions, {})
  529. self.assertEqual(volume.path, '/dev/' + volume.vid)
  530. self.assertEqual(snap_uuid, self._get_lv_uuid(volume.path))
  531. self.assertFalse(os.path.exists(path_snap), path_snap)
  532. self.loop.run_until_complete(volume.remove())
  533. def test_020_revert_last(self):
  534. ''' Test volume revert'''
  535. config = {
  536. 'name': 'root',
  537. 'pool': self.pool.name,
  538. 'save_on_stop': True,
  539. 'rw': True,
  540. 'revisions_to_keep': 2,
  541. 'size': qubes.config.defaults['root_img_size'],
  542. }
  543. vm = qubes.tests.storage.TestVM(self)
  544. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  545. self.loop.run_until_complete(volume.create())
  546. self.loop.run_until_complete(volume.start())
  547. self.loop.run_until_complete(volume.stop())
  548. self.loop.run_until_complete(volume.start())
  549. self.loop.run_until_complete(volume.stop())
  550. self.assertEqual(len(volume.revisions), 2)
  551. revisions = volume.revisions
  552. revision_id = max(revisions.keys())
  553. current_path = volume.path
  554. current_uuid = self._get_lv_uuid(volume.path)
  555. rev_uuid = self._get_lv_uuid(volume.vid + '-' + revision_id)
  556. self.assertFalse(volume.is_dirty())
  557. self.assertNotEqual(current_uuid, rev_uuid)
  558. self.loop.run_until_complete(volume.revert())
  559. path_snap = '/dev/' + volume._vid_snap
  560. self.assertFalse(os.path.exists(path_snap), path_snap)
  561. self.assertEqual(current_path, volume.path)
  562. new_uuid = self._get_lv_origin_uuid(volume.path)
  563. self.assertEqual(new_uuid, rev_uuid)
  564. self.assertEqual(volume.revisions, revisions)
  565. self.loop.run_until_complete(volume.remove())
  566. def test_021_revert_earlier(self):
  567. ''' Test volume revert'''
  568. config = {
  569. 'name': 'root',
  570. 'pool': self.pool.name,
  571. 'save_on_stop': True,
  572. 'rw': True,
  573. 'revisions_to_keep': 2,
  574. 'size': qubes.config.defaults['root_img_size'],
  575. }
  576. vm = qubes.tests.storage.TestVM(self)
  577. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  578. self.loop.run_until_complete(volume.create())
  579. self.loop.run_until_complete(volume.start())
  580. self.loop.run_until_complete(volume.stop())
  581. self.loop.run_until_complete(volume.start())
  582. self.loop.run_until_complete(volume.stop())
  583. self.assertEqual(len(volume.revisions), 2)
  584. revisions = volume.revisions
  585. revision_id = min(revisions.keys())
  586. current_path = volume.path
  587. current_uuid = self._get_lv_uuid(volume.path)
  588. rev_uuid = self._get_lv_uuid(volume.vid + '-' + revision_id)
  589. self.assertFalse(volume.is_dirty())
  590. self.assertNotEqual(current_uuid, rev_uuid)
  591. self.loop.run_until_complete(volume.revert(revision_id))
  592. path_snap = '/dev/' + volume._vid_snap
  593. self.assertFalse(os.path.exists(path_snap), path_snap)
  594. self.assertEqual(current_path, volume.path)
  595. new_uuid = self._get_lv_origin_uuid(volume.path)
  596. self.assertEqual(new_uuid, rev_uuid)
  597. self.assertEqual(volume.revisions, revisions)
  598. self.loop.run_until_complete(volume.remove())
  599. def test_030_import_data(self):
  600. ''' Test volume import'''
  601. config = {
  602. 'name': 'root',
  603. 'pool': self.pool.name,
  604. 'save_on_stop': True,
  605. 'rw': True,
  606. 'revisions_to_keep': 2,
  607. 'size': qubes.config.defaults['root_img_size'],
  608. }
  609. vm = qubes.tests.storage.TestVM(self)
  610. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  611. self.loop.run_until_complete(volume.create())
  612. current_uuid = self._get_lv_uuid(volume.path)
  613. self.assertFalse(volume.is_dirty())
  614. import_path = self.loop.run_until_complete(volume.import_data())
  615. import_uuid = self._get_lv_uuid(import_path)
  616. self.assertNotEqual(current_uuid, import_uuid)
  617. # success - commit data
  618. self.loop.run_until_complete(volume.import_data_end(True))
  619. new_current_uuid = self._get_lv_uuid(volume.path)
  620. self.assertEqual(new_current_uuid, import_uuid)
  621. revisions = volume.revisions
  622. self.assertEqual(len(revisions), 1)
  623. revision = revisions.popitem()[0]
  624. self.assertEqual(current_uuid,
  625. self._get_lv_uuid(volume.vid + '-' + revision))
  626. self.assertFalse(os.path.exists(import_path), import_path)
  627. self.loop.run_until_complete(volume.remove())
  628. def test_031_import_data_fail(self):
  629. ''' Test volume import'''
  630. config = {
  631. 'name': 'root',
  632. 'pool': self.pool.name,
  633. 'save_on_stop': True,
  634. 'rw': True,
  635. 'revisions_to_keep': 2,
  636. 'size': qubes.config.defaults['root_img_size'],
  637. }
  638. vm = qubes.tests.storage.TestVM(self)
  639. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  640. self.loop.run_until_complete(volume.create())
  641. current_uuid = self._get_lv_uuid(volume.path)
  642. self.assertFalse(volume.is_dirty())
  643. import_path = self.loop.run_until_complete(volume.import_data())
  644. import_uuid = self._get_lv_uuid(import_path)
  645. self.assertNotEqual(current_uuid, import_uuid)
  646. # fail - discard data
  647. self.loop.run_until_complete(volume.import_data_end(False))
  648. new_current_uuid = self._get_lv_uuid(volume.path)
  649. self.assertEqual(new_current_uuid, current_uuid)
  650. revisions = volume.revisions
  651. self.assertEqual(len(revisions), 0)
  652. self.assertFalse(os.path.exists(import_path), import_path)
  653. self.loop.run_until_complete(volume.remove())
  654. def test_032_import_volume_same_pool(self):
  655. '''Import volume from the same pool'''
  656. # source volume
  657. config = {
  658. 'name': 'root',
  659. 'pool': self.pool.name,
  660. 'save_on_stop': True,
  661. 'rw': True,
  662. 'revisions_to_keep': 2,
  663. 'size': qubes.config.defaults['root_img_size'],
  664. }
  665. vm = qubes.tests.storage.TestVM(self)
  666. source_volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  667. self.loop.run_until_complete(source_volume.create())
  668. source_uuid = self._get_lv_uuid(source_volume.path)
  669. # destination volume
  670. config = {
  671. 'name': 'root2',
  672. 'pool': self.pool.name,
  673. 'save_on_stop': True,
  674. 'rw': True,
  675. 'revisions_to_keep': 2,
  676. 'size': qubes.config.defaults['root_img_size'],
  677. }
  678. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  679. volume.log = unittest.mock.Mock()
  680. with unittest.mock.patch('time.time') as mock_time:
  681. mock_time.side_effect = [1521065905]
  682. self.loop.run_until_complete(volume.create())
  683. self.assertEqual(volume.revisions, {})
  684. uuid_before = self._get_lv_uuid(volume.path)
  685. with unittest.mock.patch('time.time') as mock_time:
  686. mock_time.side_effect = [1521065906]
  687. self.loop.run_until_complete(
  688. volume.import_volume(source_volume))
  689. uuid_after = self._get_lv_uuid(volume.path)
  690. self.assertNotEqual(uuid_after, uuid_before)
  691. # also should be different than source volume (clone, not the same LV)
  692. self.assertNotEqual(uuid_after, source_uuid)
  693. self.assertEqual(self._get_lv_origin_uuid(volume.path), source_uuid)
  694. expected_revisions = {
  695. '1521065906-back': '2018-03-14T22:18:26',
  696. }
  697. self.assertEqual(volume.revisions, expected_revisions)
  698. self.loop.run_until_complete(volume.remove())
  699. self.loop.run_until_complete(source_volume.remove())
  700. def test_033_import_volume_different_pool(self):
  701. '''Import volume from a different pool'''
  702. source_volume = unittest.mock.Mock()
  703. # destination volume
  704. config = {
  705. 'name': 'root2',
  706. 'pool': self.pool.name,
  707. 'save_on_stop': True,
  708. 'rw': True,
  709. 'revisions_to_keep': 2,
  710. 'size': qubes.config.defaults['root_img_size'],
  711. }
  712. vm = qubes.tests.storage.TestVM(self)
  713. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  714. volume.log = unittest.mock.Mock()
  715. with unittest.mock.patch('time.time') as mock_time:
  716. mock_time.side_effect = [1521065905]
  717. self.loop.run_until_complete(volume.create())
  718. self.assertEqual(volume.revisions, {})
  719. uuid_before = self._get_lv_uuid(volume.path)
  720. with tempfile.NamedTemporaryFile() as source_volume_file:
  721. source_volume_file.write(b'test-content')
  722. source_volume_file.flush()
  723. source_volume.size = 16 * 1024 * 1024 # 16MiB
  724. source_volume.export.return_value = source_volume_file.name
  725. with unittest.mock.patch('time.time') as mock_time:
  726. mock_time.side_effect = [1521065906]
  727. self.loop.run_until_complete(
  728. volume.import_volume(source_volume))
  729. uuid_after = self._get_lv_uuid(volume.path)
  730. self.assertNotEqual(uuid_after, uuid_before)
  731. self.assertEqual(volume.size, 16 * 1024 * 1024)
  732. volume_content = subprocess.check_output(['sudo', 'cat', volume.path])
  733. self.assertEqual(volume_content.rstrip(b'\0'), b'test-content')
  734. expected_revisions = {
  735. '1521065906-back': '2018-03-14T22:18:26',
  736. }
  737. self.assertEqual(volume.revisions, expected_revisions)
  738. self.loop.run_until_complete(volume.remove())
  739. def test_040_volatile(self):
  740. '''Volatile volume test'''
  741. config = {
  742. 'name': 'volatile',
  743. 'pool': self.pool.name,
  744. 'rw': True,
  745. 'size': qubes.config.defaults['root_img_size'],
  746. }
  747. vm = qubes.tests.storage.TestVM(self)
  748. volume = self.app.get_pool(self.pool.name).init_volume(vm, config)
  749. # volatile volume don't need any file, verify should succeed
  750. self.assertTrue(volume.verify())
  751. self.loop.run_until_complete(volume.create())
  752. self.assertTrue(volume.verify())
  753. self.assertFalse(volume.save_on_stop)
  754. self.assertFalse(volume.snap_on_start)
  755. path = volume.path
  756. self.assertEqual(path, '/dev/' + volume.vid)
  757. self.assertFalse(os.path.exists(path))
  758. self.loop.run_until_complete(volume.start())
  759. self.assertTrue(os.path.exists(path))
  760. vol_uuid = self._get_lv_uuid(path)
  761. self.loop.run_until_complete(volume.start())
  762. self.assertTrue(os.path.exists(path))
  763. vol_uuid2 = self._get_lv_uuid(path)
  764. self.assertNotEqual(vol_uuid, vol_uuid2)
  765. self.loop.run_until_complete(volume.stop())
  766. self.assertFalse(os.path.exists(path))
  767. def test_050_snapshot_volume(self):
  768. ''' Test snapshot volume creation '''
  769. config_origin = {
  770. 'name': 'root',
  771. 'pool': self.pool.name,
  772. 'save_on_stop': True,
  773. 'rw': True,
  774. 'size': qubes.config.defaults['root_img_size'],
  775. }
  776. vm = qubes.tests.storage.TestVM(self)
  777. volume_origin = self.app.get_pool(self.pool.name).init_volume(
  778. vm, config_origin)
  779. self.loop.run_until_complete(volume_origin.create())
  780. config_snapshot = {
  781. 'name': 'root2',
  782. 'pool': self.pool.name,
  783. 'snap_on_start': True,
  784. 'source': volume_origin,
  785. 'rw': True,
  786. 'size': qubes.config.defaults['root_img_size'],
  787. }
  788. volume = self.app.get_pool(self.pool.name).init_volume(
  789. vm, config_snapshot)
  790. self.assertIsInstance(volume, ThinVolume)
  791. self.assertEqual(volume.name, 'root2')
  792. self.assertEqual(volume.pool, self.pool.name)
  793. self.assertEqual(volume.size, qubes.config.defaults['root_img_size'])
  794. # only origin volume really needs to exist, verify should succeed
  795. # even before create
  796. self.assertTrue(volume.verify())
  797. self.loop.run_until_complete(volume.create())
  798. path = volume.path
  799. self.assertEqual(path, '/dev/' + volume.vid)
  800. self.assertFalse(os.path.exists(path), path)
  801. self.loop.run_until_complete(volume.start())
  802. # snapshot volume isn't considered dirty at any time
  803. self.assertFalse(volume.is_dirty())
  804. # not outdated yet
  805. self.assertFalse(volume.is_outdated())
  806. origin_uuid = self._get_lv_uuid(volume_origin.path)
  807. snap_origin_uuid = self._get_lv_origin_uuid(volume._vid_snap)
  808. self.assertEqual(origin_uuid, snap_origin_uuid)
  809. # now make it outdated
  810. self.loop.run_until_complete(volume_origin.start())
  811. self.loop.run_until_complete(volume_origin.stop())
  812. self.assertTrue(volume.is_outdated())
  813. origin_uuid = self._get_lv_uuid(volume_origin.path)
  814. self.assertNotEqual(origin_uuid, snap_origin_uuid)
  815. self.loop.run_until_complete(volume.stop())
  816. # stopped volume is never outdated
  817. self.assertFalse(volume.is_outdated())
  818. path = volume.path
  819. self.assertFalse(os.path.exists(path), path)
  820. path = '/dev/' + volume._vid_snap
  821. self.assertFalse(os.path.exists(path), path)
  822. self.loop.run_until_complete(volume.remove())
  823. self.loop.run_until_complete(volume_origin.remove())
  824. def test_100_pool_list_volumes(self):
  825. config = {
  826. 'name': 'root',
  827. 'pool': self.pool.name,
  828. 'save_on_stop': True,
  829. 'rw': True,
  830. 'revisions_to_keep': 2,
  831. 'size': qubes.config.defaults['root_img_size'],
  832. }
  833. config2 = config.copy()
  834. vm = qubes.tests.storage.TestVM(self)
  835. volume1 = self.app.get_pool(self.pool.name).init_volume(vm, config)
  836. self.loop.run_until_complete(volume1.create())
  837. config2['name'] = 'private'
  838. volume2 = self.app.get_pool(self.pool.name).init_volume(vm, config2)
  839. self.loop.run_until_complete(volume2.create())
  840. # create some revisions
  841. self.loop.run_until_complete(volume1.start())
  842. self.loop.run_until_complete(volume1.stop())
  843. # and have one in dirty state
  844. self.loop.run_until_complete(volume2.start())
  845. self.assertIn(volume1, list(self.pool.volumes))
  846. self.assertIn(volume2, list(self.pool.volumes))
  847. self.loop.run_until_complete(volume1.remove())
  848. self.assertNotIn(volume1, list(self.pool.volumes))
  849. self.assertIn(volume2, list(self.pool.volumes))
  850. self.loop.run_until_complete(volume2.remove())
  851. self.assertNotIn(volume1, list(self.pool.volumes))
  852. self.assertNotIn(volume1, list(self.pool.volumes))
  853. @skipUnlessLvmPoolExists
  854. class TC_01_ThinPool(ThinPoolBase, qubes.tests.SystemTestCase):
  855. ''' Sanity tests for :py:class:`qubes.storage.lvm.ThinPool` '''
  856. def setUp(self):
  857. super(TC_01_ThinPool, self).setUp()
  858. self.init_default_template()
  859. def test_004_import(self):
  860. template_vm = self.app.default_template
  861. name = self.make_vm_name('import')
  862. vm = self.app.add_new_vm(qubes.vm.templatevm.TemplateVM, name=name,
  863. label='red')
  864. vm.clone_properties(template_vm)
  865. self.loop.run_until_complete(
  866. vm.clone_disk_files(template_vm, pool=self.pool.name))
  867. for v_name, volume in vm.volumes.items():
  868. if volume.save_on_stop:
  869. expected = "/dev/{!s}/vm-{!s}-{!s}".format(
  870. DEFAULT_LVM_POOL.split('/')[0], vm.name, v_name)
  871. self.assertEqual(volume.path, expected)
  872. with self.assertNotRaises(qubes.exc.QubesException):
  873. self.loop.run_until_complete(vm.start())
  874. def test_005_create_appvm(self):
  875. vm = self.app.add_new_vm(cls=qubes.vm.appvm.AppVM,
  876. name=self.make_vm_name('appvm'), label='red')
  877. self.loop.run_until_complete(vm.create_on_disk(pool=self.pool.name))
  878. for v_name, volume in vm.volumes.items():
  879. if volume.save_on_stop:
  880. expected = "/dev/{!s}/vm-{!s}-{!s}".format(
  881. DEFAULT_LVM_POOL.split('/')[0], vm.name, v_name)
  882. self.assertEqual(volume.path, expected)
  883. with self.assertNotRaises(qubes.exc.QubesException):
  884. self.loop.run_until_complete(vm.start())
  885. @skipUnlessLvmPoolExists
  886. class TC_02_StorageHelpers(ThinPoolBase):
  887. def setUp(self):
  888. xml_path = '/tmp/qubes-test.xml'
  889. self.app = qubes.Qubes.create_empty_store(store=xml_path,
  890. clockvm=None,
  891. updatevm=None,
  892. offline_mode=True,
  893. )
  894. os.environ['QUBES_XML_PATH'] = xml_path
  895. super(TC_02_StorageHelpers, self).setUp()
  896. # reset cache
  897. qubes.storage.DirectoryThinPool._thin_pool = {}
  898. self.thin_dir = tempfile.TemporaryDirectory()
  899. subprocess.check_call(
  900. ['sudo', 'lvcreate', '-q', '-V', '32M',
  901. '-T', DEFAULT_LVM_POOL, '-n',
  902. 'test-file-pool'], stdout=subprocess.DEVNULL)
  903. self.thin_dev = '/dev/{}/test-file-pool'.format(
  904. DEFAULT_LVM_POOL.split('/')[0])
  905. subprocess.check_call(
  906. ['sudo', 'mkfs.ext4', '-q', self.thin_dev])
  907. subprocess.check_call(['sudo', 'mount', self.thin_dev,
  908. self.thin_dir.name])
  909. subprocess.check_call(['sudo', 'chmod', '777',
  910. self.thin_dir.name])
  911. def tearDown(self):
  912. subprocess.check_call(['sudo', 'umount', self.thin_dir.name])
  913. subprocess.check_call(
  914. ['sudo', 'lvremove', '-q', '-f', self.thin_dev],
  915. stdout = subprocess.DEVNULL)
  916. self.thin_dir.cleanup()
  917. super(TC_02_StorageHelpers, self).tearDown()
  918. os.unlink(self.app.store)
  919. del self.app
  920. for attr in dir(self):
  921. if isinstance(getattr(self, attr), qubes.vm.BaseVM):
  922. delattr(self, attr)
  923. def test_000_search_thin_pool(self):
  924. pool = qubes.storage.search_pool_containing_dir(
  925. self.app.pools.values(), self.thin_dir.name)
  926. self.assertEqual(pool, self.pool)
  927. def test_001_search_none(self):
  928. pool = qubes.storage.search_pool_containing_dir(
  929. self.app.pools.values(), '/tmp')
  930. self.assertIsNone(pool)
  931. def test_002_search_subdir(self):
  932. subdir = os.path.join(self.thin_dir.name, 'some-dir')
  933. os.mkdir(subdir)
  934. pool = qubes.storage.search_pool_containing_dir(
  935. self.app.pools.values(), subdir)
  936. self.assertEqual(pool, self.pool)
  937. def test_003_search_file_pool(self):
  938. subdir = os.path.join(self.thin_dir.name, 'some-dir')
  939. file_pool_config = {
  940. 'name': 'test-file-pool',
  941. 'driver': 'file',
  942. 'dir_path': subdir
  943. }
  944. pool2 = self.app.add_pool(**file_pool_config)
  945. pool = qubes.storage.search_pool_containing_dir(
  946. self.app.pools.values(), subdir)
  947. self.assertEqual(pool, pool2)
  948. pool = qubes.storage.search_pool_containing_dir(
  949. self.app.pools.values(), self.thin_dir.name)
  950. self.assertEqual(pool, self.pool)