diff --git a/qubes/storage/lvm.py b/qubes/storage/lvm.py index 99a89f477..0f17a08fc 100644 --- a/qubes/storage/lvm.py +++ b/qubes/storage/lvm.py @@ -223,16 +223,21 @@ def _parse_lvm_cache(lvm_output): line = line.decode().strip() pool_name, pool_lv, name, size, usage_percent, attr, \ origin, metadata_size, metadata_percent = line.split(';', 8) - if '' in [pool_name, name, size, usage_percent]: + if '' in [pool_name, name, size]: continue name = pool_name + "/" + name size = int(size[:-1]) # Remove 'B' suffix - usage = int(size / 100 * float(usage_percent)) + if usage_percent: + usage = int(size / 100 * float(usage_percent)) + else: + # keep cached usage for non-active volumes + usage = size_cache.get(name, {}).get('usage') if metadata_size: metadata_size = int(metadata_size[:-1]) + if metadata_percent: metadata_usage = int(metadata_size / 100 * float(metadata_percent)) else: - metadata_usage = None + metadata_usage = size_cache.get(name, {}).get('metadata_usage') result[name] = {'size': size, 'usage': usage, 'pool_lv': pool_lv, 'attr': attr, 'origin': origin, 'metadata_size': metadata_size, 'metadata_usage': metadata_usage} @@ -277,6 +282,7 @@ def init_cache_coro(log=logging.getLogger('qubes.storage.lvm')): return _parse_lvm_cache(out) size_cache_time = 0 +size_cache = {} size_cache = init_cache() @@ -357,7 +363,7 @@ def size(self, _): @asyncio.coroutine def _reset(self): - ''' Resets a volatile volume ''' + """ Resets a volatile volume. Leave it in an active state """ assert not self.snap_on_start and not self.save_on_stop, \ "Not a volatile volume" self.log.debug('Resetting volatile %s', self.vid) @@ -367,7 +373,7 @@ def _reset(self): except qubes.storage.StoragePoolException: pass # pylint: disable=protected-access - cmd = ['create', self.pool._pool_id, self.vid.split('/')[1], + cmd = ['create-activate', self.pool._pool_id, self.vid.split('/')[1], str(self.size)] yield from qubes_lvm_coro(cmd, self.log) @@ -420,7 +426,7 @@ def _commit(self, vid_to_commit=None, keep=False): vid_to_commit = self._vid_snap assert self._lock.locked() - if not os.path.exists('/dev/' + vid_to_commit): + if vid_to_commit not in size_cache: # nothing to commit return @@ -466,21 +472,21 @@ def create(self): def remove(self): assert self.vid try: - if os.path.exists('/dev/' + self._vid_snap): + if self._vid_snap in size_cache: cmd = ['remove', self._vid_snap] yield from qubes_lvm_coro(cmd, self.log) except AttributeError: pass try: - if os.path.exists('/dev/' + self._vid_import): + if self._vid_import in size_cache: cmd = ['remove', self._vid_import] yield from qubes_lvm_coro(cmd, self.log) except AttributeError: pass yield from self._remove_revisions(self.revisions.keys()) - if not os.path.exists(self.path): + if self._vid_current not in size_cache: return cmd = ['remove', self.path] yield from qubes_lvm_coro(cmd, self.log) @@ -491,10 +497,22 @@ def remove(self): def export(self): ''' Returns an object that can be `open()`. ''' # make sure the device node is available - qubes_lvm(['activate', self.path], self.log) + qubes_lvm(['activate', self._vid_current], self.log) devpath = self.path return devpath + def export_end(self, path): + ''' Returns an object that can be `open()`. ''' + # release device node + assert path == '/dev/' + self._vid_current + try: + qubes_lvm(['deactivate', path[len('/dev/'):]], self.log) + except subprocess.CalledProcessError as e: + self.log.warning('Cannot deactivate %s - failed with code %d', + self._vid_current, e.returncode) + # device may be in use by another export + pass + @qubes.storage.Volume.locked @asyncio.coroutine def import_volume(self, src_volume): @@ -513,11 +531,12 @@ def import_volume(self, src_volume): src_volume.pool.thin_pool == self.pool.thin_pool: # NOQA yield from self._commit(src_volume.path[len('/dev/'):], keep=True) else: - cmd = ['create', + cmd = ['create-activate', self.pool._pool_id, # pylint: disable=protected-access self._vid_import.split('/')[1], str(src_volume.size)] yield from qubes_lvm_coro(cmd, self.log) + yield from reset_cache_coro() src_path = yield from qubes.utils.coro_maybe(src_volume.export()) try: cmd = ['dd', 'if=' + src_path, 'of=/dev/' + self._vid_import, @@ -525,7 +544,7 @@ def import_volume(self, src_volume): if not os.access('/dev/' + self._vid_import, os.W_OK) or \ not os.access(src_path, os.R_OK): cmd.insert(0, 'sudo') - + print(cmd) p = yield from asyncio.create_subprocess_exec(*cmd) yield from p.wait() finally: @@ -551,7 +570,7 @@ def import_data(self, size): format(self.vid)) self.abort_if_import_in_progress() # pylint: disable=protected-access - cmd = ['create', self.pool._pool_id, self._vid_import.split('/')[1], + cmd = ['create-activate', self.pool._pool_id, self._vid_import.split('/')[1], str(size)] yield from qubes_lvm_coro(cmd, self.log) yield from reset_cache_coro() @@ -562,7 +581,9 @@ def import_data(self, size): @asyncio.coroutine def import_data_end(self, success): '''Either commit imported data, or discard temporary volume''' - if not os.path.exists('/dev/' + self._vid_import): + print(self._vid_import) + print(list(size_cache.keys())) + if self._vid_import not in size_cache: raise qubes.storage.StoragePoolException( 'No import operation in progress on {}'.format(self.vid)) if success: @@ -570,11 +591,11 @@ def import_data_end(self, success): else: cmd = ['remove', self._vid_import] yield from qubes_lvm_coro(cmd, self.log) + yield from reset_cache_coro() def abort_if_import_in_progress(self): try: - devpath = '/dev/' + self._vid_import - if os.path.exists(devpath): + if self._vid_import in size_cache: raise qubes.storage.StoragePoolException( 'Import operation in progress on {}'.format(self.vid)) except AttributeError: # self._vid_import @@ -583,7 +604,9 @@ def abort_if_import_in_progress(self): def is_dirty(self): if self.save_on_stop: - return os.path.exists('/dev/' + self._vid_snap) + print('is_dirty({}): {}'.format(self._vid_snap, list(size_cache.keys()))) + subprocess.check_output(['sudo', 'lvs']) + return self._vid_snap in size_cache return False def is_outdated(self): @@ -605,9 +628,9 @@ def revert(self, revision=None): if revision is None: revision = \ max(self.revisions.items(), key=_revision_sort_key)[0] - old_path = '/dev/' + self.vid + '-' + revision - if not os.path.exists(old_path): - msg = "Volume {!s} has no {!s}".format(self, old_path) + old_vid = self.vid + '-' + revision + if old_vid not in size_cache: + msg = "Volume {!s} has no {!s}".format(self, old_vid) raise qubes.storage.StoragePoolException(msg) if self.vid in size_cache: @@ -640,21 +663,30 @@ def resize(self, size): return if self.is_dirty(): + print('dirty') cmd = ['extend', self._vid_snap, str(size)] yield from qubes_lvm_coro(cmd, self.log) elif hasattr(self, '_vid_import') and \ - os.path.exists('/dev/' + self._vid_import): + self._vid_import in size_cache: + print('in import') cmd = ['extend', self._vid_import, str(size)] yield from qubes_lvm_coro(cmd, self.log) elif self.save_on_stop and not self.snap_on_start: + print('normal') cmd = ['extend', self._vid_current, str(size)] yield from qubes_lvm_coro(cmd, self.log) + print('done') self._size = size yield from reset_cache_coro() @asyncio.coroutine def _snapshot(self): + """ + Make an *active* snapshot of the source volume. + + :return: + """ try: cmd = ['remove', self._vid_snap] yield from qubes_lvm_coro(cmd, self.log) @@ -662,9 +694,9 @@ def _snapshot(self): pass if self.source is None: - cmd = ['clone', self._vid_current, self._vid_snap] + cmd = ['clone-activate', self._vid_current, self._vid_snap] else: - cmd = ['clone', self.source.path, self._vid_snap] + cmd = ['clone-activate', self.source.path, self._vid_snap] yield from qubes_lvm_coro(cmd, self.log) @qubes.storage.Volume.locked @@ -675,6 +707,9 @@ def start(self): if self.snap_on_start or self.save_on_stop: if not self.save_on_stop or not self.is_dirty(): yield from self._snapshot() + else: + cmd = ['activate', self._vid_snap] + yield from qubes_lvm_coro(cmd, self.log) else: yield from self._reset() finally: @@ -706,12 +741,7 @@ def verify(self): vid = self.source.path[len('/dev/'):] else: vid = self._vid_current - try: - vol_info = size_cache[vid] - if vol_info['attr'][4] != 'a': - raise qubes.storage.StoragePoolException( - 'volume {} not active'.format(vid)) - except KeyError: + if vid not in size_cache: raise qubes.storage.StoragePoolException( 'volume {} missing'.format(vid)) return True @@ -755,25 +785,28 @@ def _get_lvm_cmdline(cmd): :return array of str appropriate for subprocess.Popen ''' action = cmd[0] + activate = '-ay' if '-activate' in action else '-an' if action == 'remove': lvm_cmd = ['lvremove', '-f', cmd[1]] - elif action == 'clone': - lvm_cmd = ['lvcreate', '-kn', '-ay', '-s', cmd[1], '-n', cmd[2]] - elif action == 'create': - lvm_cmd = ['lvcreate', '-T', cmd[1], '-kn', '-ay', '-n', cmd[2], '-V', - str(cmd[3]) + 'B'] + elif action in ('clone', 'clone-activate'): + lvm_cmd = ['lvcreate', '-K', '-ky', activate, '-s', cmd[1], '-n', cmd[2]] + elif action in ('create', 'create-activate'): + lvm_cmd = ['lvcreate', '-T', cmd[1], '-K', '-ky', activate, '-n', cmd[2], + '-V', str(cmd[3]) + 'B'] elif action == 'extend': size = int(cmd[2]) / (1024 * 1024) lvm_cmd = ["lvextend", "-L%s" % size, cmd[1]] elif action == 'activate': - lvm_cmd = ['lvchange', '-ay', cmd[1]] + lvm_cmd = ['lvchange', '-K', '-ay', cmd[1]] + elif action == 'deactivate': + lvm_cmd = ['lvchange', '-an', cmd[1]] elif action == 'rename': lvm_cmd = ['lvrename', cmd[1], cmd[2]] else: raise NotImplementedError('unsupported action: ' + action) if lvm_is_very_old: # old lvm in trusty image used there does not support -k option - lvm_cmd = [x for x in lvm_cmd if x != '-kn'] + lvm_cmd = [x for x in lvm_cmd if x not in ('-kn', '-ky')] if os.getuid() != 0: cmd = ['sudo', 'lvm'] + lvm_cmd else: @@ -825,6 +858,7 @@ def qubes_lvm_coro(cmd, log=logging.getLogger('qubes.storage.lvm')): close_fds=True, env=environ) _, _ = yield from p.communicate() cmd = _get_lvm_cmdline(cmd) + print('calling {}'.format(' '.join(cmd))) p = yield from asyncio.create_subprocess_exec(*cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, diff --git a/qubes/tests/storage_lvm.py b/qubes/tests/storage_lvm.py index 9f2dea069..5ddd5ce49 100644 --- a/qubes/tests/storage_lvm.py +++ b/qubes/tests/storage_lvm.py @@ -103,6 +103,8 @@ def cleanup_test_volumes(self): 'sudo', 'lvremove', '-f', '/'.join([self.pool.volume_group, volume]) )) self.loop.run_until_complete(p.wait()) + qubes.storage.lvm.reset_cache() + def tearDown(self): ''' Remove the default lvm pool if it was created only for this test ''' @@ -155,6 +157,22 @@ def tearDown(self): if isinstance(getattr(self, attr), qubes.vm.BaseVM): delattr(self, attr) + def assertVolumeExists(self, vid): + try: + subprocess.check_call(['sudo', 'lvs', '--noheadings', vid], + stdout=subprocess.PIPE) + except subprocess.CalledProcessError: + self.fail('volume {} does not exist'.format(vid)) + + def assertVolumeNotExists(self, vid): + try: + subprocess.check_call(['sudo', 'lvs', '--noheadings', vid], + stdout=subprocess.PIPE) + except subprocess.CalledProcessError: + pass + else: + self.fail('volume {} does exist'.format(vid)) + def test_000_default_thin_pool(self): ''' Check whether :py:data`DEFAULT_LVM_POOL` exists. This pool is created by default, if at installation time LVM + Thin was chosen. @@ -178,8 +196,7 @@ def test_001_origin_volume(self): self.assertEqual(volume.pool, self.pool.name) self.assertEqual(volume.size, qubes.config.defaults['root_img_size']) self.loop.run_until_complete(volume.create()) - path = "/dev/%s" % volume.vid - self.assertTrue(os.path.exists(path), path) + self.assertVolumeExists(volume.vid) self.loop.run_until_complete(volume.remove()) def test_003_read_write_volume(self): @@ -198,8 +215,7 @@ def test_003_read_write_volume(self): self.assertEqual(volume.pool, self.pool.name) self.assertEqual(volume.size, qubes.config.defaults['root_img_size']) self.loop.run_until_complete(volume.create()) - path = "/dev/%s" % volume.vid - self.assertTrue(os.path.exists(path), path) + self.assertVolumeExists(volume.vid) self.loop.run_until_complete(volume.remove()) def test_004_size(self): @@ -228,15 +244,12 @@ def test_005_usage(self): self.assertEqual(usage, int(pool_size * pool_usage / 100)) def _get_size(self, path): - if os.getuid() != 0: - return int( - subprocess.check_output( - ['sudo', 'blockdev', '--getsize64', path])) - fd = os.open(path, os.O_RDONLY) - try: - return os.lseek(fd, 0, os.SEEK_END) - finally: - os.close(fd) + subprocess.check_call( + ['sudo', 'lvs', '-o', 'name,size', path]) + return int( + subprocess.check_output( + ['sudo', 'lvs', '--noheadings', '--nosuffix', + '--units', 'b', '-o', 'size', path])) def test_006_resize(self): config = { @@ -256,6 +269,7 @@ def test_006_resize(self): self.assertEqual(self._get_size(path), new_size) self.assertEqual(volume.size, new_size) + @qubes.tests.wait_on_fail def test_007_resize_running(self): old_size = 32 * 1024**2 config = { @@ -314,6 +328,7 @@ def test_008_commit(self): volume = self.app.get_pool(self.pool.name).init_volume(vm, config) self.loop.run_until_complete(volume.create()) path_snap = '/dev/' + volume._vid_snap + self.assertVolumeNotExists(volume._vid_snap) self.assertFalse(os.path.exists(path_snap), path_snap) origin_uuid = self._get_lv_uuid(volume.path) self.loop.run_until_complete(volume.start()) @@ -322,7 +337,7 @@ def test_008_commit(self): path = volume.path self.assertTrue(path.startswith('/dev/' + volume.vid), '{} does not start with /dev/{}'.format(path, volume.vid)) - self.assertTrue(os.path.exists(path), path) + self.assertVolumeExists(path) self.loop.run_until_complete(volume.remove()) def test_009_interrupted_commit(self): @@ -376,8 +391,7 @@ def test_009_interrupted_commit(self): self.assertEqual(volume.revisions, expected_revisions) self.assertEqual(volume.path, '/dev/' + volume.vid) self.assertEqual(snap_uuid, self._get_lv_uuid(volume.path)) - self.assertFalse(os.path.exists(path_snap), path_snap) - + self.assertVolumeNotExists(path_snap) self.loop.run_until_complete(volume.remove()) def test_010_migration1(self): @@ -404,7 +418,7 @@ def test_010_migration1(self): orig_uuids[rev] = self._get_lv_uuid(volume.vid + rev) qubes.storage.lvm.reset_cache() path_snap = '/dev/' + volume._vid_snap - self.assertFalse(os.path.exists(path_snap), path_snap) + self.assertVolumeNotExists(path_snap) expected_revisions = { revisions[1].lstrip('-'): '2018-03-14T22:18:24', revisions[2].lstrip('-'): '2018-03-14T22:18:25', @@ -419,7 +433,7 @@ def test_010_migration1(self): self.assertEqual(orig_uuids[''], snap_origin_uuid) path = volume.path self.assertEqual(path, '/dev/' + volume.vid) - self.assertTrue(os.path.exists(path), path) + self.assertVolumeExists(path) with unittest.mock.patch('time.time') as mock_time: mock_time.side_effect = ('1521065906', '1521065907') @@ -433,7 +447,8 @@ def test_010_migration1(self): self.assertEqual(volume.path, '/dev/' + volume.vid) path_snap = '/dev/' + volume._vid_snap self.assertFalse(os.path.exists(path_snap), path_snap) - self.assertTrue(os.path.exists('/dev/' + volume.vid)) + self.assertVolumeNotExists(path_snap) + self.assertVolumeExists(volume.vid) self.assertEqual(self._get_lv_uuid(volume.path), snap_uuid) prev_path = '/dev/' + volume.vid + revisions[3] self.assertEqual(self._get_lv_uuid(prev_path), orig_uuids['']) @@ -466,8 +481,7 @@ def test_011_migration2(self): qubes_lvm(cmd) orig_uuids[rev] = self._get_lv_uuid(volume.vid + rev) qubes.storage.lvm.reset_cache() - path_snap = '/dev/' + volume._vid_snap - self.assertTrue(os.path.exists(path_snap), path_snap) + self.assertVolumeExists(volume._vid_snap) expected_revisions = {} self.assertEqual(volume.revisions, expected_revisions) self.assertEqual(volume.path, '/dev/' + volume.vid) @@ -475,7 +489,7 @@ def test_011_migration2(self): path = volume.path self.assertEqual(path, '/dev/' + volume.vid) - self.assertTrue(os.path.exists(path), path) + self.assertVolumeExists(path) with unittest.mock.patch('time.time') as mock_time: mock_time.side_effect = ('1521065906', '1521065907') @@ -486,9 +500,8 @@ def test_011_migration2(self): } self.assertEqual(volume.revisions, expected_revisions) self.assertEqual(volume.path, '/dev/' + volume.vid) - path_snap = '/dev/' + volume._vid_snap - self.assertFalse(os.path.exists(path_snap), path_snap) - self.assertTrue(os.path.exists('/dev/' + volume.vid)) + self.assertVolumeNotExists(volume._vid_snap) + self.assertVolumeExists(volume.vid) self.assertEqual(self._get_lv_uuid(volume.path), orig_uuids['-snap']) prev_path = '/dev/' + volume.vid + revisions[2] self.assertEqual(self._get_lv_uuid(prev_path), orig_uuids['']) @@ -496,7 +509,8 @@ def test_011_migration2(self): self.loop.run_until_complete(volume.remove()) for rev in revisions: path = '/dev/' + volume.vid + rev - self.assertFalse(os.path.exists(path), path) + print(path, rev) + self.assertVolumeNotExists(path) def test_012_migration3(self): '''VM started with old code, started again with new, stopped with new''' @@ -521,8 +535,7 @@ def test_012_migration3(self): qubes_lvm(cmd) orig_uuids[rev] = self._get_lv_uuid(volume.vid + rev) qubes.storage.lvm.reset_cache() - path_snap = '/dev/' + volume._vid_snap - self.assertTrue(os.path.exists(path_snap), path_snap) + self.assertVolumeExists(volume._vid_snap) expected_revisions = {} self.assertEqual(volume.revisions, expected_revisions) self.assertTrue(volume.path, '/dev/' + volume.vid) @@ -539,6 +552,7 @@ def test_012_migration3(self): for rev in revisions: path = '/dev/' + volume.vid + rev self.assertFalse(os.path.exists(path), path) + self.assertVolumeNotExists(path) def test_013_migration4(self): '''revisions_to_keep=0, VM started with old code, stopped with new''' @@ -563,8 +577,7 @@ def test_013_migration4(self): qubes_lvm(cmd) orig_uuids[rev] = self._get_lv_uuid(volume.vid + rev) qubes.storage.lvm.reset_cache() - path_snap = '/dev/' + volume._vid_snap - self.assertTrue(os.path.exists(path_snap), path_snap) + self.assertVolumeExists(volume._vid_snap) expected_revisions = {} self.assertEqual(volume.revisions, expected_revisions) self.assertEqual(volume.path, '/dev/' + volume.vid) @@ -580,7 +593,7 @@ def test_013_migration4(self): self.loop.run_until_complete(volume.remove()) for rev in revisions: path = '/dev/' + volume.vid + rev - self.assertFalse(os.path.exists(path), path) + self.assertVolumeNotExists(path) def test_014_commit_keep_0(self): ''' Test volume changes commit, with revisions_to_keep=0''' @@ -693,8 +706,7 @@ def test_021_revert_earlier(self): self.assertFalse(volume.is_dirty()) self.assertNotEqual(current_uuid, rev_uuid) self.loop.run_until_complete(volume.revert(revision_id)) - path_snap = '/dev/' + volume._vid_snap - self.assertFalse(os.path.exists(path_snap), path_snap) + self.assertVolumeNotExists(volume._vid_snap) self.assertEqual(current_path, volume.path) new_uuid = self._get_lv_origin_uuid(volume.path) self.assertEqual(new_uuid, rev_uuid) @@ -730,7 +742,7 @@ def test_030_import_data(self): revision = revisions.popitem()[0] self.assertEqual(current_uuid, self._get_lv_uuid(volume.vid + '-' + revision)) - self.assertFalse(os.path.exists(import_path), import_path) + self.assertVolumeNotExists(import_path) self.loop.run_until_complete(volume.remove()) @@ -759,7 +771,7 @@ def test_031_import_data_fail(self): self.assertEqual(new_current_uuid, current_uuid) revisions = volume.revisions self.assertEqual(len(revisions), 0) - self.assertFalse(os.path.exists(import_path), import_path) + self.assertVolumeNotExists(import_path) self.loop.run_until_complete(volume.remove()) @@ -888,7 +900,7 @@ def test_034_import_data_empty(self): 'sudo', 'touch', import_path)) self.loop.run_until_complete(p.wait()) self.loop.run_until_complete(volume.import_data_end(True)) - self.assertFalse(os.path.exists(import_path), import_path) + self.assertVolumeNotExists(import_path) p = self.loop.run_until_complete(asyncio.create_subprocess_exec( 'sudo', 'cat', volume.path, stdout=subprocess.PIPE @@ -937,15 +949,19 @@ def test_040_volatile(self): path = volume.path self.assertEqual(path, '/dev/' + volume.vid) self.assertFalse(os.path.exists(path)) + self.assertVolumeNotExists(path) self.loop.run_until_complete(volume.start()) self.assertTrue(os.path.exists(path)) + self.assertVolumeExists(path) vol_uuid = self._get_lv_uuid(path) self.loop.run_until_complete(volume.start()) self.assertTrue(os.path.exists(path)) + self.assertVolumeExists(path) vol_uuid2 = self._get_lv_uuid(path) self.assertNotEqual(vol_uuid, vol_uuid2) self.loop.run_until_complete(volume.stop()) self.assertFalse(os.path.exists(path)) + self.assertVolumeNotExists(path) def test_050_snapshot_volume(self): ''' Test snapshot volume creation ''' @@ -981,6 +997,7 @@ def test_050_snapshot_volume(self): path = volume.path self.assertEqual(path, '/dev/' + volume.vid) self.assertFalse(os.path.exists(path), path) + self.assertVolumeNotExists(path) self.loop.run_until_complete(volume.start()) # snapshot volume isn't considered dirty at any time self.assertFalse(volume.is_dirty()) @@ -1001,9 +1018,9 @@ def test_050_snapshot_volume(self): # stopped volume is never outdated self.assertFalse(volume.is_outdated()) path = volume.path - self.assertFalse(os.path.exists(path), path) + self.assertVolumeNotExists(path) path = '/dev/' + volume._vid_snap - self.assertFalse(os.path.exists(path), path) + self.assertVolumeNotExists(path) self.loop.run_until_complete(volume.remove()) self.loop.run_until_complete(volume_origin.remove()) diff --git a/run-tests b/run-tests index 59c96eb48..97119696e 100755 --- a/run-tests +++ b/run-tests @@ -11,11 +11,15 @@ install_rpm_deps () { : # we don’t actually care if this succeeds } if { command -pv rpm && command -pv dnf; }>/dev/null; then install_rpm_deps; fi -CLEANUP_LVM= + +cleanup_lvm() { + sudo --non-interactive $(dirname "$0")/ci/lvm-manage cleanup-lvm "$DEFAULT_LVM_POOL" +} + name=$(dirname "$0") if sudo --non-interactive "$name/ci/lvm-manage" setup-lvm vg$$/pool; then export DEFAULT_LVM_POOL=vg$$/pool - CLEANUP_LVM=yes + trap cleanup_lvm EXIT fi : "${PYTHON:=python3}" @@ -30,8 +34,3 @@ export PYTHONPATH "${PYTHON}" setup.py egg_info --egg-base "${TESTPYTHONPATH}" "${PYTHON}" -m coverage run --rcfile=ci/coveragerc -m qubes.tests.run "$@" -retcode=$? -if [ -n "$CLEANUP_LVM" ]; then - sudo --non-interactive $(dirname "$0")/ci/lvm-manage cleanup-lvm "$DEFAULT_LVM_POOL" -fi -exit $retcode