backup.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. #!/usr/bin/python
  2. # vim: fileencoding=utf-8
  3. #
  4. # The Qubes OS Project, https://www.qubes-os.org/
  5. #
  6. # Copyright (C) 2014-2015
  7. # Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
  8. # Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
  9. #
  10. # This program is free software; you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation; either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License along
  21. # with this program; if not, write to the Free Software Foundation, Inc.,
  22. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  23. #
  24. import os
  25. import unittest
  26. import sys
  27. import qubes
  28. import qubes.exc
  29. import qubes.tests
  30. import qubes.vm.appvm
  31. import qubes.vm.templatevm
  32. class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase):
  33. def test_000_basic_backup(self):
  34. vms = self.create_backup_vms()
  35. self.make_backup(vms)
  36. self.remove_vms(reversed(vms))
  37. self.restore_backup()
  38. for vm in vms:
  39. self.assertIn(vm.name, self.app.domains)
  40. def test_001_compressed_backup(self):
  41. vms = self.create_backup_vms()
  42. self.make_backup(vms, compressed=True)
  43. self.remove_vms(reversed(vms))
  44. self.restore_backup()
  45. for vm in vms:
  46. self.assertIn(vm.name, self.app.domains)
  47. def test_002_encrypted_backup(self):
  48. vms = self.create_backup_vms()
  49. self.make_backup(vms, encrypted=True)
  50. self.remove_vms(reversed(vms))
  51. self.restore_backup()
  52. for vm in vms:
  53. self.assertIn(vm.name, self.app.domains)
  54. def test_003_compressed_encrypted_backup(self):
  55. vms = self.create_backup_vms()
  56. self.make_backup(vms, compressed=True, encrypted=True)
  57. self.remove_vms(reversed(vms))
  58. self.restore_backup()
  59. for vm in vms:
  60. self.assertIn(vm.name, self.app.domains)
  61. def test_004_sparse_multipart(self):
  62. vms = []
  63. vmname = self.make_vm_name('testhvm2')
  64. if self.verbose:
  65. print >>sys.stderr, "-> Creating %s" % vmname
  66. hvmtemplate = self.app.add_new_vm(
  67. qubes.vm.templatevm.TemplateVM, name=vmname, hvm=True, label='red')
  68. hvmtemplate.create_on_disk()
  69. self.fill_image(os.path.join(hvmtemplate.dir_path, '00file'),
  70. 195*1024*1024-4096*3)
  71. self.fill_image(hvmtemplate.private_img, 195*1024*1024-4096*3)
  72. self.fill_image(hvmtemplate.root_img, 1024*1024*1024, sparse=True)
  73. vms.append(hvmtemplate)
  74. self.app.save()
  75. self.make_backup(vms)
  76. self.remove_vms(reversed(vms))
  77. self.restore_backup()
  78. for vm in vms:
  79. self.assertIn(vm.name, self.app.domains)
  80. # TODO check vm.backup_timestamp
  81. def test_005_compressed_custom(self):
  82. vms = self.create_backup_vms()
  83. self.make_backup(vms, compressed="bzip2")
  84. self.remove_vms(reversed(vms))
  85. self.restore_backup()
  86. for vm in vms:
  87. self.assertIn(vm.name, self.app.domains)
  88. def test_100_backup_dom0_no_restore(self):
  89. # do not write it into dom0 home itself...
  90. os.mkdir('/var/tmp/test-backup')
  91. self.backupdir = '/var/tmp/test-backup'
  92. self.make_backup([self.app.domains[0]])
  93. # TODO: think of some safe way to test restore...
  94. def test_200_restore_over_existing_directory(self):
  95. """
  96. Regression test for #1386
  97. :return:
  98. """
  99. vms = self.create_backup_vms()
  100. self.make_backup(vms)
  101. self.remove_vms(reversed(vms))
  102. test_dir = vms[0].dir_path
  103. os.mkdir(test_dir)
  104. with open(os.path.join(test_dir, 'some-file.txt'), 'w') as f:
  105. f.write('test file\n')
  106. self.restore_backup(
  107. expect_errors=[
  108. '*** Directory {} already exists! It has been moved'.format(
  109. test_dir)
  110. ])
  111. def test_210_auto_rename(self):
  112. """
  113. Test for #869
  114. :return:
  115. """
  116. vms = self.create_backup_vms()
  117. self.make_backup(vms)
  118. self.restore_backup(options={
  119. 'rename_conflicting': True
  120. })
  121. for vm in vms:
  122. with self.assertNotRaises(
  123. (qubes.exc.QubesVMNotFoundError, KeyError)):
  124. restored_vm = self.app.domains[vm.name + '1']
  125. if vm.netvm and not vm.property_is_default('netvm'):
  126. self.assertEqual(restored_vm.netvm.name, vm.netvm.name + '1')
  127. class TC_10_BackupVMMixin(qubes.tests.BackupTestsMixin):
  128. def setUp(self):
  129. super(TC_10_BackupVMMixin, self).setUp()
  130. self.backupvm = self.app.add_new_vm(
  131. qubes.vm.appvm.AppVM,
  132. label='red',
  133. name=self.make_vm_name('backupvm'),
  134. template=self.template
  135. )
  136. self.backupvm.create_on_disk()
  137. def test_100_send_to_vm_file_with_spaces(self):
  138. vms = self.create_backup_vms()
  139. self.backupvm.start()
  140. self.backupvm.run("mkdir '/var/tmp/backup directory'", wait=True)
  141. self.make_backup(vms, target_vm=self.backupvm,
  142. compressed=True, encrypted=True,
  143. target='/var/tmp/backup directory')
  144. self.remove_vms(reversed(vms))
  145. p = self.backupvm.run("ls /var/tmp/backup*/qubes-backup*",
  146. passio_popen=True)
  147. (backup_path, _) = p.communicate()
  148. backup_path = backup_path.strip()
  149. self.restore_backup(source=backup_path,
  150. appvm=self.backupvm)
  151. def test_110_send_to_vm_command(self):
  152. vms = self.create_backup_vms()
  153. self.backupvm.start()
  154. self.make_backup(vms, target_vm=self.backupvm,
  155. compressed=True, encrypted=True,
  156. target='dd of=/var/tmp/backup-test')
  157. self.remove_vms(reversed(vms))
  158. self.restore_backup(source='dd if=/var/tmp/backup-test',
  159. appvm=self.backupvm)
  160. def test_110_send_to_vm_no_space(self):
  161. """
  162. Check whether backup properly report failure when no enough space is
  163. available
  164. :return:
  165. """
  166. vms = self.create_backup_vms()
  167. self.backupvm.start()
  168. retcode = self.backupvm.run(
  169. # Debian 7 has too old losetup to handle loop-control device
  170. "mknod /dev/loop0 b 7 0;"
  171. "truncate -s 50M /home/user/backup.img && "
  172. "mkfs.ext4 -F /home/user/backup.img && "
  173. "mkdir /home/user/backup && "
  174. "mount /home/user/backup.img /home/user/backup -o loop &&"
  175. "chmod 777 /home/user/backup",
  176. user="root", wait=True)
  177. if retcode != 0:
  178. raise RuntimeError("Failed to prepare backup directory")
  179. with self.assertRaises(qubes.exc.QubesException):
  180. self.make_backup(vms, target_vm=self.backupvm,
  181. compressed=False, encrypted=True,
  182. target='/home/user/backup',
  183. expect_failure=True)
  184. def load_tests(loader, tests, pattern):
  185. try:
  186. app = qubes.Qubes()
  187. templates = [vm.name for vm in app.domains if
  188. isinstance(vm, qubes.vm.templatevm.TemplateVM)]
  189. except OSError:
  190. templates = []
  191. for template in templates:
  192. tests.addTests(loader.loadTestsFromTestCase(
  193. type(
  194. 'TC_10_BackupVM_' + template,
  195. (TC_10_BackupVMMixin, qubes.tests.QubesTestCase),
  196. {'template': template})))
  197. return tests