Browse Source

storage: implement admin.vm.volume.Import as volume.import_data

Use newly introduced payload_stream= argument to qubesd_call to pass
data directly from some file-like object - without loading it all into
memory.

QubesOS/qubes-issues#853
Marek Marczykowski-Górecki 7 years ago
parent
commit
50237d4953
2 changed files with 32 additions and 2 deletions
  1. 19 2
      qubesadmin/storage.py
  2. 13 0
      qubesadmin/tests/storage.py

+ 19 - 2
qubesadmin/storage.py

@@ -20,6 +20,7 @@
 
 '''Storage subsystem.'''
 
+
 class Volume(object):
     '''Storage volume.'''
     def __init__(self, app, pool=None, vid=None, vm=None, vm_name=None):
@@ -47,17 +48,23 @@ class Volume(object):
         self._vm_name = vm_name
         self._info = None
 
-    def _qubesd_call(self, func_name, payload=None):
+    def _qubesd_call(self, func_name, payload=None, payload_stream=None):
         '''Make a call to qubesd regarding this volume
 
         :param str func_name: API function name, like `Info` or `Resize`
         :param bytes payload: Payload to send.
+        :param file payload_stream: Stream to pipe payload from. Only one of
+        `payload` and `payload_stream` can be used.
         '''
         if self._vm is not None:
             method = 'admin.vm.volume.' + func_name
             dest = self._vm
             arg = self._vm_name
         else:
+            if payload_stream:
+                raise NotImplementedError(
+                    'payload_stream not implemented for '
+                    'admin.pool.volume.* calls')
             method = 'admin.pool.volume.' + func_name
             dest = 'dom0'
             arg = self._pool
@@ -65,7 +72,8 @@ class Volume(object):
                 payload = self._vid.encode('ascii') + b' ' + payload
             else:
                 payload = self._vid.encode('ascii')
-        return self.app.qubesd_call(dest, method, arg, payload)
+        return self.app.qubesd_call(dest, method, arg, payload=payload,
+            payload_stream=payload_stream)
 
     def _fetch_info(self, force=True):
         '''Fetch volume properties
@@ -178,6 +186,15 @@ class Volume(object):
             raise TypeError('revision must be a str')
         self._qubesd_call('Revert', revision.encode('ascii'))
 
+    def import_data(self, stream):
+        ''' Import volume data from a given file-like object.
+
+        This function override existing volume content
+
+        :param stream: file-like object, must support fileno()
+        '''
+        self._qubesd_call('Import', payload_stream=stream)
+
 
 class Pool(object):
     ''' A Pool is used to manage different kind of volumes (File

+ 13 - 0
qubesadmin/tests/storage.py

@@ -17,6 +17,7 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with this program; if not, see <http://www.gnu.org/licenses/>.
+import subprocess
 
 import qubesadmin.tests
 import qubesadmin.storage
@@ -150,6 +151,15 @@ class TestVMVolume(qubesadmin.tests.QubesTestCase):
         self.vol.revert('snapid1')
         self.assertAllCalled()
 
+    def test_040_import_data(self):
+        self.app.expected_calls[
+            ('test-vm', 'admin.vm.volume.Import', 'volname', b'some-data')] = \
+            b'0\x00'
+        input_proc = subprocess.Popen(['echo', '-n', 'some-data'],
+            stdout=subprocess.PIPE)
+        self.vol.import_data(input_proc.stdout)
+        self.assertAllCalled()
+
 
 class TestPoolVolume(TestVMVolume):
     def setUp(self):
@@ -233,6 +243,9 @@ class TestPoolVolume(TestVMVolume):
         self.vol.revert('snapid1')
         self.assertAllCalled()
 
+    def test_040_import_data(self):
+        self.skipTest('admin.pool.vm.Import not supported')
+
 
 class TestPool(qubesadmin.tests.QubesTestCase):
     def test_000_list(self):