Skip to content

Commit 16834f9

Browse files
committed
Merge remote-tracking branch 'origin/pr/358'
* origin/pr/358: Improve `qvm-backup-restore --paranoid-mode`
2 parents bbfa32e + 302533f commit 16834f9

File tree

4 files changed

+58
-3
lines changed

4 files changed

+58
-3
lines changed

doc/manpages/qvm-backup-restore.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ Options
101101
- dom0 home directory (desktop environment settings)
102102
- PCI devices assignments
103103

104+
This operation requires `qubes-core-admin-client` package in the DisposableVM
105+
104106
.. option:: --auto-close
105107

106108
When running with --paranoid-mode (see above), automatically close restore

qubesadmin/backup/dispvm.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,10 +276,27 @@ def run(self):
276276
lock = qubesadmin.utils.LockFile(LOCKFILE, True)
277277
lock.acquire()
278278
try:
279+
self.app.log.info("Starting restore process in a DisposableVM...")
279280
self.create_dispvm()
280281
self.clear_old_tags()
281282
self.register_backup_source()
282283
self.dispvm.start()
284+
try:
285+
self.app.log.debug(
286+
"Checking for existence of qubes-core-admin-client"
287+
)
288+
self.dispvm.run("command -v qvm-backup-restore")
289+
except subprocess.CalledProcessError:
290+
raise qubesadmin.exc.QubesException(
291+
'qvm-backup-restore tool '
292+
'missing in {} template, install qubes-core-admin-client '
293+
'package there'.format(
294+
getattr(self.dispvm.template,
295+
'template',
296+
self.dispvm.template).name)
297+
)
298+
self.app.log.info("When operation completes, close its window "
299+
"manually.")
283300
self.dispvm.run_service_for_stdio('qubes.WaitForSession')
284301
if self.args.pass_file:
285302
self.args.pass_file = self.transfer_pass_file(

qubesadmin/tests/backup/dispvm.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ def test_100_run(self):
330330
self.assertEqual(len(getattr(obj, m).mock_calls), 1)
331331
self.assertEqual(obj.dispvm.mock_calls, [
332332
call.start(),
333+
call.run('command -v qvm-backup-restore'),
333334
call.run_service_for_stdio('qubes.WaitForSession'),
334335
call.tags.add('backup-restore-mgmt'),
335336
call.run_with_args('terminal', 'qvm-backup-restore', 'args',
@@ -368,6 +369,7 @@ def test_101_run_pass_file(self):
368369
self.assertEqual(len(getattr(obj, m).mock_calls), 1)
369370
self.assertEqual(obj.dispvm.mock_calls, [
370371
call.start(),
372+
call.run('command -v qvm-backup-restore'),
371373
call.run_service_for_stdio('qubes.WaitForSession'),
372374
call.tags.add('backup-restore-mgmt'),
373375
call.run_with_args('terminal', 'qvm-backup-restore', 'args',
@@ -405,6 +407,7 @@ def test_102_run_error(self):
405407
self.assertEqual(len(getattr(obj, m).mock_calls), 1)
406408
self.assertEqual(obj.dispvm.mock_calls, [
407409
call.start(),
410+
call.run('command -v qvm-backup-restore'),
408411
call.run_service_for_stdio('qubes.WaitForSession'),
409412
call.tags.add('backup-restore-mgmt'),
410413
call.run_with_args('terminal', 'qvm-backup-restore', 'args',
@@ -414,3 +417,39 @@ def test_102_run_error(self):
414417
call.kill()
415418
])
416419
obj.transfer_pass_file.assert_not_called()
420+
421+
def test_103_missing_package(self):
422+
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
423+
b'0\0dom0 class=AdminVM state=Running\n'
424+
b'fedora-25 class=TemplateVM state=Halted\n'
425+
)
426+
args = unittest.mock.Mock(backup_location='/backup/path',
427+
pass_file=None,
428+
appvm=None)
429+
obj = RestoreInDisposableVM(self.app, args)
430+
methods = ['create_dispvm', 'clear_old_tags', 'register_backup_source',
431+
'finalize_tags']
432+
for m in methods:
433+
setattr(obj, m, unittest.mock.Mock())
434+
obj.transfer_pass_file = unittest.mock.Mock()
435+
obj.dispvm = unittest.mock.Mock()
436+
obj.dispvm.run = unittest.mock.Mock()
437+
obj.dispvm.run.side_effect = subprocess.CalledProcessError(
438+
'1',
439+
'command -v qvm-backup-restore',
440+
)
441+
with tempfile.NamedTemporaryFile() as tmp:
442+
with unittest.mock.patch('qubesadmin.backup.dispvm.LOCKFILE',
443+
tmp.name):
444+
with self.assertRaises(qubesadmin.exc.QubesException):
445+
obj.run()
446+
# pylint: disable=no-member
447+
for m in methods:
448+
self.assertEqual(len(getattr(obj, m).mock_calls), 1)
449+
self.assertEqual(obj.dispvm.mock_calls, [
450+
call.start(),
451+
call.run('command -v qvm-backup-restore'),
452+
call.tags.discard('backup-restore-mgmt'),
453+
call.kill()
454+
])
455+
obj.transfer_pass_file.assert_not_called()

qubesadmin/tools/qvm_backup_restore.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,6 @@ def main(args=None, app=None):
240240

241241
if args.paranoid_mode:
242242
args.dom0_home = False
243-
args.app.log.info("Starting restore process in a DisposableVM...")
244-
args.app.log.info("When operation completes, close its window "
245-
"manually.")
246243
restore_in_dispvm = RestoreInDisposableVM(args.app, args)
247244
try:
248245
backup_log = restore_in_dispvm.run()

0 commit comments

Comments
 (0)