core-admin-client/qubesadmin/tests/tools/qvm_clone.py
Marek Marczykowski-Górecki bcd026d141
Implement VM clone as create + copy data+metadata
This way we don't need separate admin.vm.Clone call, which is tricky to
handler properly with policy.
A VM may not have access to all the properties and other metadata, so
add ignore_errors argument, for best-effort approach (copy what is
possible). In any case, failure of cloning VM data fails the whole
operation.
When operation fails, VM is removed.

While at it, allow to specify alternative VM class - this allows
morphing one VM into another (for example AppVM -> StandaloneVM).

Adjust qvm-clone tool and tests accordingly.

QubesOS/qubes-issues#2622
2017-06-20 01:34:18 +02:00

81 lines
3.5 KiB
Python

# -*- encoding: utf8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2017 Marek Marczykowski-Górecki
# <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# 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 qubesadmin.tests
import qubesadmin.tests.tools
import qubesadmin.tools.qvm_clone
from unittest import mock
class TC_00_qvm_clone(qubesadmin.tests.QubesTestCase):
def test_000_simple(self):
self.app.clone_vm = mock.Mock()
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
b'0\x00new-vm class=AppVM state=Halted\n' \
b'test-vm class=AppVM state=Halted\n'
qubesadmin.tools.qvm_clone.main(['test-vm', 'new-vm'], app=self.app)
self.app.clone_vm.assert_called_with(self.app.domains['test-vm'],
'new-vm', new_cls=None, pool=None, pools={})
self.assertAllCalled()
def test_001_missing_vm(self):
self.app.clone_vm = mock.Mock()
with self.assertRaises(SystemExit):
with qubesadmin.tests.tools.StderrBuffer() as stderr:
qubesadmin.tools.qvm_clone.main(['test-vm'], app=self.app)
self.assertIn('NAME', stderr.getvalue())
self.assertFalse(self.app.clone_vm.called)
self.assertAllCalled()
def test_004_pool(self):
self.app.clone_vm = mock.Mock()
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
b'0\x00new-vm class=AppVM state=Halted\n' \
b'test-vm class=AppVM state=Halted\n'
qubesadmin.tools.qvm_clone.main(['-P', 'some-pool', 'test-vm', 'new-vm'],
app=self.app)
self.app.clone_vm.assert_called_with(self.app.domains['test-vm'],
'new-vm', new_cls=None, pool='some-pool', pools={})
self.assertAllCalled()
def test_005_pools(self):
self.app.clone_vm = mock.Mock()
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
b'0\x00new-vm class=AppVM state=Halted\n' \
b'test-vm class=AppVM state=Halted\n'
qubesadmin.tools.qvm_clone.main(['--pool', 'private=some-pool',
'--pool', 'volatile=other-pool', 'test-vm', 'new-vm'],
app=self.app)
self.app.clone_vm.assert_called_with(self.app.domains['test-vm'],
'new-vm', new_cls=None, pool=None, pools={'private': 'some-pool',
'volatile': 'other-pool'})
self.assertAllCalled()
def test_006_new_cls(self):
self.app.clone_vm = mock.Mock()
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
b'0\x00new-vm class=AppVM state=Halted\n' \
b'test-vm class=AppVM state=Halted\n'
qubesadmin.tools.qvm_clone.main(['--class', 'StandaloneVM',
'test-vm', 'new-vm'],
app=self.app)
self.app.clone_vm.assert_called_with(self.app.domains['test-vm'],
'new-vm', new_cls='StandaloneVM', pool=None, pools={})
self.assertAllCalled()