Browse Source

Adjust code for possibly coroutine Volume.export() and Volume.export_end()

Now Volume.export() may be a coroutine and also may be accompanied by
Volume.export_end() cleaning up after it.

See previous commits for building blocks for this.

This commit adjusts usage of Volume.export() and adds matching
Volume.export_end() throughout the code base.

Fixes QubesOS/qubes-issues#5935
Marek Marczykowski-Górecki 4 years ago
parent
commit
0bccddf1f5

+ 7 - 4
qubes/storage/__init__.py

@@ -203,6 +203,9 @@ class Volume:
             volume data. If extracting volume data require something more
             volume data. If extracting volume data require something more
             than just reading from file (for example connecting to some other
             than just reading from file (for example connecting to some other
             domain, or decompressing the data), the returned path may be a pipe.
             domain, or decompressing the data), the returned path may be a pipe.
+
+            This can be implemented as a coroutine.
+
         '''
         '''
         raise self._not_implemented("export")
         raise self._not_implemented("export")
 
 
@@ -646,14 +649,14 @@ class Storage:
                     for target in parsed_xml.xpath(
                     for target in parsed_xml.xpath(
                         "//domain/devices/disk/target")}
                         "//domain/devices/disk/target")}
 
 
+    @asyncio.coroutine
     def export(self, volume):
     def export(self, volume):
         ''' Helper function to export volume (pool.export(volume))'''
         ''' Helper function to export volume (pool.export(volume))'''
         assert isinstance(volume, (Volume, str)), \
         assert isinstance(volume, (Volume, str)), \
             "You need to pass a Volume or pool name as str"
             "You need to pass a Volume or pool name as str"
-        if isinstance(volume, Volume):
-            return volume.export()
-
-        return self.vm.volumes[volume].export()
+        if not isinstance(volume, Volume):
+            volume = self.vm.volumes[volume]
+        return (yield from qubes.utils.coro_maybe(volume.export()))
 
 
     @asyncio.coroutine
     @asyncio.coroutine
     def export_end(self, volume, export_path):
     def export_end(self, volume, export_path):

+ 8 - 2
qubes/storage/file.py

@@ -21,7 +21,7 @@
 #
 #
 
 
 ''' This module contains pool implementations backed by file images'''
 ''' This module contains pool implementations backed by file images'''
-
+import asyncio
 import os
 import os
 import os.path
 import os.path
 import re
 import re
@@ -29,6 +29,7 @@ import subprocess
 from contextlib import suppress
 from contextlib import suppress
 
 
 import qubes.storage
 import qubes.storage
+import qubes.utils
 
 
 BLKSIZE = 512
 BLKSIZE = 512
 
 
@@ -268,6 +269,7 @@ class FileVolume(qubes.storage.Volume):
         #  if domain is running
         #  if domain is running
         return self.path
         return self.path
 
 
+    @asyncio.coroutine
     def import_volume(self, src_volume):
     def import_volume(self, src_volume):
         if src_volume.snap_on_start:
         if src_volume.snap_on_start:
             raise qubes.storage.StoragePoolException(
             raise qubes.storage.StoragePoolException(
@@ -275,7 +277,11 @@ class FileVolume(qubes.storage.Volume):
                     src_volume, self))
                     src_volume, self))
         if self.save_on_stop:
         if self.save_on_stop:
             _remove_if_exists(self.path)
             _remove_if_exists(self.path)
-            copy_file(src_volume.export(), self.path)
+            path = yield from qubes.utils.coro_maybe(src_volume.export())
+            try:
+                copy_file(path, self.path)
+            finally:
+                yield from qubes.utils.coro_maybe(src_volume.export_end(path))
         return self
         return self
 
 
     def import_data(self, size):
     def import_data(self, size):

+ 13 - 9
qubes/storage/lvm.py

@@ -518,15 +518,19 @@ class ThinVolume(qubes.storage.Volume):
                    self._vid_import.split('/')[1],
                    self._vid_import.split('/')[1],
                    str(src_volume.size)]
                    str(src_volume.size)]
             yield from qubes_lvm_coro(cmd, self.log)
             yield from qubes_lvm_coro(cmd, self.log)
-            src_path = src_volume.export()
-            cmd = ['dd', 'if=' + src_path, 'of=/dev/' + self._vid_import,
-                'conv=sparse', 'status=none', 'bs=128K']
-            if not os.access('/dev/' + self._vid_import, os.W_OK) or \
-                    not os.access(src_path, os.R_OK):
-                cmd.insert(0, 'sudo')
-
-            p = yield from asyncio.create_subprocess_exec(*cmd)
-            yield from p.wait()
+            src_path = yield from qubes.utils.coro_maybe(src_volume.export())
+            try:
+                cmd = ['dd', 'if=' + src_path, 'of=/dev/' + self._vid_import,
+                    'conv=sparse', 'status=none', 'bs=128K']
+                if not os.access('/dev/' + self._vid_import, os.W_OK) or \
+                        not os.access(src_path, os.R_OK):
+                    cmd.insert(0, 'sudo')
+
+                p = yield from asyncio.create_subprocess_exec(*cmd)
+                yield from p.wait()
+            finally:
+                yield from qubes.utils.coro_maybe(
+                    src_volume.export_end(src_path))
             if p.returncode != 0:
             if p.returncode != 0:
                 cmd = ['remove', self._vid_import]
                 cmd = ['remove', self._vid_import]
                 yield from qubes_lvm_coro(cmd, self.log)
                 yield from qubes_lvm_coro(cmd, self.log)

+ 11 - 3
qubes/storage/reflink.py

@@ -35,6 +35,7 @@ import tempfile
 from contextlib import contextmanager, suppress
 from contextlib import contextmanager, suppress
 
 
 import qubes.storage
 import qubes.storage
+import qubes.utils
 
 
 FICLONE = 1074041865        # defined in <linux/fs.h>, assuming sizeof(int)==4
 FICLONE = 1074041865        # defined in <linux/fs.h>, assuming sizeof(int)==4
 LOOP_SET_CAPACITY = 0x4C07  # defined in <linux/loop.h>
 LOOP_SET_CAPACITY = 0x4C07  # defined in <linux/loop.h>
@@ -297,15 +298,22 @@ class ReflinkVolume(qubes.storage.Volume):
         _import_data_end))
         _import_data_end))
 
 
     @qubes.storage.Volume.locked
     @qubes.storage.Volume.locked
-    @_coroutinized
+    @asyncio.coroutine
     def import_volume(self, src_volume):
     def import_volume(self, src_volume):
         if self.save_on_stop:
         if self.save_on_stop:
             try:
             try:
                 success = False
                 success = False
-                _copy_file(src_volume.export(), self._path_import)
+                src_path = yield from qubes.utils.coro_maybe(
+                    src_volume.export())
+                try:
+                    yield from _coroutinized(_copy_file)(
+                        src_path, self._path_import)
+                finally:
+                    yield from qubes.utils.coro_maybe(
+                        src_volume.export_end(src_path))
                 success = True
                 success = True
             finally:
             finally:
-                self._import_data_end(success)
+                yield from _coroutinized(self._import_data_end)(success)
         return self
         return self
 
 
     def _path_revision(self, number, timestamp=None):
     def _path_revision(self, number, timestamp=None):

+ 18 - 8
qubes/tests/integ/backup.py

@@ -36,6 +36,7 @@ import qubes.exc
 import qubes.storage.lvm
 import qubes.storage.lvm
 import qubes.tests
 import qubes.tests
 import qubes.tests.storage_lvm
 import qubes.tests.storage_lvm
+import qubes.utils
 import qubes.vm
 import qubes.vm
 import qubes.vm.appvm
 import qubes.vm.appvm
 import qubes.vm.templatevm
 import qubes.vm.templatevm
@@ -106,6 +107,13 @@ class BackupTestsMixin(object):
 
 
         f.close()
         f.close()
 
 
+    def fill_image_vm(self, vm, volume, size=None, sparse=False):
+        path = self.loop.run_until_complete(vm.storage.export(volume))
+        try:
+            self.fill_image(path, size=size, sparse=sparse)
+        finally:
+            self.loop.run_until_complete(vm.storage.export_end(volume, path))
+
     # NOTE: this was create_basic_vms
     # NOTE: this was create_basic_vms
     def create_backup_vms(self, pool=None):
     def create_backup_vms(self, pool=None):
         template = self.app.default_template
         template = self.app.default_template
@@ -120,7 +128,7 @@ class BackupTestsMixin(object):
             testnet.create_on_disk(pool=pool))
             testnet.create_on_disk(pool=pool))
         testnet.features['service.ntpd'] = True
         testnet.features['service.ntpd'] = True
         vms.append(testnet)
         vms.append(testnet)
-        self.fill_image(testnet.storage.export('private'), 20*1024*1024)
+        self.fill_image_vm(testnet, 'private', 20*1024*1024)
 
 
         vmname = self.make_vm_name('test1')
         vmname = self.make_vm_name('test1')
         self.log.debug("Creating %s" % vmname)
         self.log.debug("Creating %s" % vmname)
@@ -130,7 +138,7 @@ class BackupTestsMixin(object):
         self.loop.run_until_complete(
         self.loop.run_until_complete(
             testvm1.create_on_disk(pool=pool))
             testvm1.create_on_disk(pool=pool))
         vms.append(testvm1)
         vms.append(testvm1)
-        self.fill_image(testvm1.storage.export('private'), 100 * 1024 * 1024)
+        self.fill_image_vm(testvm1, 'private', 100 * 1024 * 1024)
 
 
         vmname = self.make_vm_name('testhvm1')
         vmname = self.make_vm_name('testhvm1')
         self.log.debug("Creating %s" % vmname)
         self.log.debug("Creating %s" % vmname)
@@ -140,8 +148,7 @@ class BackupTestsMixin(object):
                                       label='red')
                                       label='red')
         self.loop.run_until_complete(
         self.loop.run_until_complete(
             testvm2.create_on_disk(pool=pool))
             testvm2.create_on_disk(pool=pool))
-        self.fill_image(testvm2.storage.export('root'), 1024 * 1024 * 1024, \
-            True)
+        self.fill_image_vm(testvm2, 'root', 1024 * 1024 * 1024, True)
         vms.append(testvm2)
         vms.append(testvm2)
 
 
         vmname = self.make_vm_name('template')
         vmname = self.make_vm_name('template')
@@ -150,7 +157,7 @@ class BackupTestsMixin(object):
             name=vmname, label='red')
             name=vmname, label='red')
         self.loop.run_until_complete(
         self.loop.run_until_complete(
             testvm3.create_on_disk(pool=pool))
             testvm3.create_on_disk(pool=pool))
-        self.fill_image(testvm3.storage.export('root'), 100 * 1024 * 1024, True)
+        self.fill_image_vm(testvm3, 'root', 100 * 1024 * 1024, True)
         vms.append(testvm3)
         vms.append(testvm3)
 
 
         vmname = self.make_vm_name('custom')
         vmname = self.make_vm_name('custom')
@@ -262,11 +269,14 @@ class BackupTestsMixin(object):
             for name, volume in vm.volumes.items():
             for name, volume in vm.volumes.items():
                 if not volume.rw or not volume.save_on_stop:
                 if not volume.rw or not volume.save_on_stop:
                     continue
                     continue
-                vol_path = volume.export()
+                vol_path = self.loop.run_until_complete(
+                    qubes.utils.coro_maybe(volume.export()))
                 hasher = hashlib.sha1()
                 hasher = hashlib.sha1()
                 with open(vol_path, 'rb') as afile:
                 with open(vol_path, 'rb') as afile:
                     for buf in iter(lambda: afile.read(4096000), b''):
                     for buf in iter(lambda: afile.read(4096000), b''):
                         hasher.update(buf)
                         hasher.update(buf)
+                self.loop.run_until_complete(
+                    qubes.utils.coro_maybe(volume.export_end(vol_path)))
                 hashes[vm.name][name] = hasher.hexdigest()
                 hashes[vm.name][name] = hasher.hexdigest()
         return hashes
         return hashes
 
 
@@ -382,9 +392,9 @@ class TC_00_Backup(BackupTestsMixin, qubes.tests.SystemTestCase):
             self.fill_image(
             self.fill_image(
                 os.path.join(self.hvmtemplate.dir_path, '00file'),
                 os.path.join(self.hvmtemplate.dir_path, '00file'),
                 195 * 1024 * 1024 - 4096 * 3)
                 195 * 1024 * 1024 - 4096 * 3)
-            self.fill_image(self.hvmtemplate.storage.export('private'),
+            self.fill_image_vm(self.hvmtemplate, 'private',
                             195 * 1024 * 1024 - 4096 * 3)
                             195 * 1024 * 1024 - 4096 * 3)
-            self.fill_image(self.hvmtemplate.storage.export('root'), 1024 * 1024 * 1024,
+            self.fill_image_vm(self.hvmtemplate, 'root', 1024 * 1024 * 1024,
                             sparse=True)
                             sparse=True)
             vms.append(self.hvmtemplate)
             vms.append(self.hvmtemplate)
             self.app.save()
             self.app.save()

+ 8 - 3
qubes/tests/integ/basic.py

@@ -518,9 +518,14 @@ class TC_03_QvmRevertTemplateChanges(qubes.tests.SystemTestCase):
         self.app.save()
         self.app.save()
 
 
     def get_rootimg_checksum(self):
     def get_rootimg_checksum(self):
-        return subprocess.check_output(
-            ['sha1sum', self.test_template.volumes['root'].export()]).\
-            decode().split(' ')[0]
+        path = self.loop.run_until_complete(
+            self.test_template.storage.export('root'))
+        try:
+            return subprocess.check_output(['sha1sum', path]).\
+                decode().split(' ')[0]
+        finally:
+            self.loop.run_until_complete(
+                self.test_template.storage.export_end('root', path))
 
 
     def _do_test(self):
     def _do_test(self):
         checksum_before = self.get_rootimg_checksum()
         checksum_before = self.get_rootimg_checksum()