Skip to content

Commit

Permalink
storage: rework clone as two-stage operation
Browse files Browse the repository at this point in the history
Split clone to two Admin API calls - one to the source volume, then
other to destination.
  • Loading branch information
marmarek committed Jul 5, 2017
1 parent 88de4f7 commit c6eb4c4
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 30 deletions.
13 changes: 5 additions & 8 deletions qubesadmin/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,14 +221,11 @@ def clone(self, source):
'''

# 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())

# get a token from source volume
token = source._qubesd_call('CloneFrom')
# and use it to actually clone volume data
self._qubesd_call('CloneTo', payload=token)


class Pool(object):
Expand Down
16 changes: 12 additions & 4 deletions qubesadmin/tests/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,10 @@ def clone_setup_common_calls(self, src, dst):
(src, 'admin.vm.volume.List', None, None)] = \
b'0\x00root\nprivate\nvolatile\nkernel\n'
self.app.expected_calls[
(src, 'admin.vm.volume.Clone', 'private', dst.encode())] = \
(src, 'admin.vm.volume.CloneFrom', 'private', None)] = \
b'0\x00token-private'
self.app.expected_calls[
(dst, 'admin.vm.volume.CloneTo', 'private', b'token-private')] = \
b'0\x00'

def test_030_clone(self):
Expand Down Expand Up @@ -364,7 +367,10 @@ def test_034_clone_class_change(self):
b'save_on_stop=True\n' \
b'snap_on_start=False\n'
self.app.expected_calls[
('test-vm', 'admin.vm.volume.Clone', 'root', b'new-name')] = \
('test-vm', 'admin.vm.volume.CloneFrom', 'root', None)] = \
b'0\x00token-root'
self.app.expected_calls[
('new-name', 'admin.vm.volume.CloneTo', 'root', b'token-root')] = \
b'0\x00'
new_vm = self.app.clone_vm('test-vm', 'new-name',
new_cls='StandaloneVM')
Expand Down Expand Up @@ -482,7 +488,8 @@ def test_040_clone_ignore_errors_storage(self):
self.app.expected_calls[('dom0', 'admin.vm.Create.AppVM',
'test-template', b'name=new-name label=red')] = b'0\x00'
self.app.expected_calls[
('test-vm', 'admin.vm.volume.Clone', 'private', b'new-name')] = \
('new-name', 'admin.vm.volume.CloneTo', 'private',
b'token-private')] = \
b'2\0QubesException\0\0something happened\0'
self.app.expected_calls[('new-name', 'admin.vm.Remove', None, None)] = \
b'0\x00'
Expand All @@ -504,7 +511,8 @@ def test_041_clone_fail_storage(self):
self.app.expected_calls[('dom0', 'admin.vm.Create.AppVM',
'test-template', b'name=new-name label=red')] = b'0\x00'
self.app.expected_calls[
('test-vm', 'admin.vm.volume.Clone', 'private', b'new-name')] = \
('new-name', 'admin.vm.volume.CloneTo', 'private',
b'token-private')] = \
b'2\0QubesException\0\0something happened\0'
self.app.expected_calls[('new-name', 'admin.vm.Remove', None, None)] = \
b'0\x00'
Expand Down
29 changes: 11 additions & 18 deletions qubesadmin/tests/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,10 @@ def test_040_import_data(self):

def test_050_clone(self):
self.app.expected_calls[
('source-vm', 'admin.vm.volume.Clone', 'volname', b'test-vm')] = \
('source-vm', 'admin.vm.volume.CloneFrom', 'volname', None)] = \
b'0\x00abcdef'
self.app.expected_calls[
('test-vm', 'admin.vm.volume.CloneTo', 'volname', b'abcdef')] = \
b'0\x00'
self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \
Expand All @@ -174,17 +177,6 @@ def test_050_clone(self):
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):
Expand Down Expand Up @@ -273,13 +265,14 @@ def test_040_import_data(self):

def test_050_clone(self):
self.app.expected_calls[
('dom0', 'admin.vm.List', None, None)] = \
b'0\x00source-vm class=AppVM state=Halted\n'
('dom0', 'admin.pool.volume.CloneFrom', 'test-pool',
b'volid')] = b'0\x00abcdef'
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'])
('dom0', 'admin.pool.volume.CloneTo', 'test-pool',
b'some-id abcdef')] = b'0\x00'
source_vol = qubesadmin.storage.Volume(self.app, pool='test-pool',
vid='volid')
self.vol.clone(source_vol)
self.assertAllCalled()

def test_050_clone_wrong_volume(self):
Expand Down

0 comments on commit c6eb4c4

Please sign in to comment.