storage: add volume clone method
Clone volume without retrieving all the data. QubesOS/qubes-issues#2622
This commit is contained in:
parent
90141a1bef
commit
998a42703f
@ -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
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user