Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/manpages/qvm-backup-restore.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ Options
- dom0 home directory (desktop environment settings)
- PCI devices assignments

This operation requires `qubes-core-admin-client` package in the DisposableVM

.. option:: --auto-close

When running with --paranoid-mode (see above), automatically close restore
Expand Down
17 changes: 17 additions & 0 deletions qubesadmin/backup/dispvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,27 @@ def run(self):
lock = qubesadmin.utils.LockFile(LOCKFILE, True)
lock.acquire()
try:
self.app.log.info("Starting restore process in a DisposableVM...")
self.create_dispvm()
self.clear_old_tags()
self.register_backup_source()
self.dispvm.start()
try:
self.app.log.debug(
"Checking for existence of qubes-core-admin-client"
)
self.dispvm.run("command -v qvm-backup-restore")
except subprocess.CalledProcessError:
raise qubesadmin.exc.QubesException(
'qvm-backup-restore tool '
'missing in {} template, install qubes-core-admin-client '
'package there'.format(
getattr(self.dispvm.template,
'template',
self.dispvm.template).name)
)
self.app.log.info("When operation completes, close its window "
"manually.")
self.dispvm.run_service_for_stdio('qubes.WaitForSession')
if self.args.pass_file:
self.args.pass_file = self.transfer_pass_file(
Expand Down
39 changes: 39 additions & 0 deletions qubesadmin/tests/backup/dispvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ def test_100_run(self):
self.assertEqual(len(getattr(obj, m).mock_calls), 1)
self.assertEqual(obj.dispvm.mock_calls, [
call.start(),
call.run('command -v qvm-backup-restore'),
call.run_service_for_stdio('qubes.WaitForSession'),
call.tags.add('backup-restore-mgmt'),
call.run_with_args('terminal', 'qvm-backup-restore', 'args',
Expand Down Expand Up @@ -368,6 +369,7 @@ def test_101_run_pass_file(self):
self.assertEqual(len(getattr(obj, m).mock_calls), 1)
self.assertEqual(obj.dispvm.mock_calls, [
call.start(),
call.run('command -v qvm-backup-restore'),
call.run_service_for_stdio('qubes.WaitForSession'),
call.tags.add('backup-restore-mgmt'),
call.run_with_args('terminal', 'qvm-backup-restore', 'args',
Expand Down Expand Up @@ -405,6 +407,7 @@ def test_102_run_error(self):
self.assertEqual(len(getattr(obj, m).mock_calls), 1)
self.assertEqual(obj.dispvm.mock_calls, [
call.start(),
call.run('command -v qvm-backup-restore'),
call.run_service_for_stdio('qubes.WaitForSession'),
call.tags.add('backup-restore-mgmt'),
call.run_with_args('terminal', 'qvm-backup-restore', 'args',
Expand All @@ -414,3 +417,39 @@ def test_102_run_error(self):
call.kill()
])
obj.transfer_pass_file.assert_not_called()

def test_103_missing_package(self):
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
b'0\0dom0 class=AdminVM state=Running\n'
b'fedora-25 class=TemplateVM state=Halted\n'
)
args = unittest.mock.Mock(backup_location='/backup/path',
pass_file=None,
appvm=None)
obj = RestoreInDisposableVM(self.app, args)
methods = ['create_dispvm', 'clear_old_tags', 'register_backup_source',
'finalize_tags']
for m in methods:
setattr(obj, m, unittest.mock.Mock())
obj.transfer_pass_file = unittest.mock.Mock()
obj.dispvm = unittest.mock.Mock()
obj.dispvm.run = unittest.mock.Mock()
obj.dispvm.run.side_effect = subprocess.CalledProcessError(
'1',
'command -v qvm-backup-restore',
)
with tempfile.NamedTemporaryFile() as tmp:
with unittest.mock.patch('qubesadmin.backup.dispvm.LOCKFILE',
tmp.name):
with self.assertRaises(qubesadmin.exc.QubesException):
obj.run()
# pylint: disable=no-member
for m in methods:
self.assertEqual(len(getattr(obj, m).mock_calls), 1)
self.assertEqual(obj.dispvm.mock_calls, [
call.start(),
call.run('command -v qvm-backup-restore'),
call.tags.discard('backup-restore-mgmt'),
call.kill()
])
obj.transfer_pass_file.assert_not_called()
3 changes: 0 additions & 3 deletions qubesadmin/tools/qvm_backup_restore.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,6 @@ def main(args=None, app=None):

if args.paranoid_mode:
args.dom0_home = False
args.app.log.info("Starting restore process in a DisposableVM...")
args.app.log.info("When operation completes, close its window "
"manually.")
restore_in_dispvm = RestoreInDisposableVM(args.app, args)
try:
backup_log = restore_in_dispvm.run()
Expand Down