storage: add volume clone method

Clone volume without retrieving all the data.

QubesOS/qubes-issues#2622
This commit is contained in:
Marek Marczykowski-Górecki 2017-06-19 03:02:43 +02:00
parent 90141a1bef
commit 998a42703f
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
2 changed files with 64 additions and 1 deletions

View File

@ -93,6 +93,11 @@ class Volume(object):
return self.pool == other.pool and self.vid == other.vid
return NotImplemented
@property
def name(self):
'''per-VM volume name, if available'''
return self._vm_name
@property
def pool(self):
'''Storage volume pool name.'''
@ -195,6 +200,26 @@ class Volume(object):
'''
self._qubesd_call('Import', payload_stream=stream)
def clone(self, source):
''' Clone data from sane volume of another VM.
This function override existing volume content.
This operation is implemented for VM volumes - those in vm.volumes
collection (not pool.volumes).
:param source: source volume object
'''
# pylint: disable=protected-access
if source._vm_name is None or self._vm_name is None:
raise NotImplementedError(
'Operation implemented only for VM volumes')
if source._vm_name != self._vm_name:
raise ValueError('Source and target volume must have the same type')
# this call is to *source* volume, because we extract data from there
source._qubesd_call('Clone', payload=str(self._vm).encode())
class Pool(object):
''' A Pool is used to manage different kind of volumes (File

View File

@ -161,6 +161,30 @@ class TestVMVolume(qubesadmin.tests.QubesTestCase):
input_proc.stdout.close()
self.assertAllCalled()
def test_050_clone(self):
self.app.expected_calls[
('source-vm', 'admin.vm.volume.Clone', 'volname', b'test-vm')] = \
b'0\x00'
self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \
b'0\x00source-vm class=AppVM state=Halted\n'
self.app.expected_calls[
('source-vm', 'admin.vm.volume.List', None, None)] = \
b'0\x00volname\nother\n'
self.vol.clone(self.app.domains['source-vm'].volumes['volname'])
self.assertAllCalled()
def test_050_clone_wrong_volume(self):
self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \
b'0\x00source-vm class=AppVM state=Halted\n'
self.app.expected_calls[
('source-vm', 'admin.vm.volume.List', None, None)] = \
b'0\x00volname\nother\n'
with self.assertRaises(ValueError):
self.vol.clone(self.app.domains['source-vm'].volumes['other'])
self.assertAllCalled()
class TestPoolVolume(TestVMVolume):
def setUp(self):
@ -245,7 +269,21 @@ class TestPoolVolume(TestVMVolume):
self.assertAllCalled()
def test_040_import_data(self):
self.skipTest('admin.pool.vm.Import not supported')
self.skipTest('admin.pool.volume.Import not supported')
def test_050_clone(self):
self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \
b'0\x00source-vm class=AppVM state=Halted\n'
self.app.expected_calls[
('source-vm', 'admin.vm.volume.List', None, None)] = \
b'0\x00volname\nother\n'
with self.assertRaises(NotImplementedError):
self.vol.clone(self.app.domains['source-vm'].volumes['volname'])
self.assertAllCalled()
def test_050_clone_wrong_volume(self):
self.skipTest('admin.pool.volume.Clone not supported')
class TestPool(qubesadmin.tests.QubesTestCase):