backup.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  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 sys
  26. import qubes
  27. import qubes.exc
  28. import qubes.tests
  29. import qubes.vm.appvm
  30. import qubes.vm.templatevm
  31. class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase):
  32. def test_000_basic_backup(self):
  33. vms = self.create_backup_vms()
  34. self.make_backup(vms)
  35. self.remove_vms(reversed(vms))
  36. self.restore_backup()
  37. for vm in vms:
  38. self.assertIn(vm.name, self.app.domains)
  39. restored_vm = self.app.domains[vm.name]
  40. for prop in ('name', 'kernel',
  41. 'memory', 'maxmem', 'kernelopts',
  42. 'services', 'vcpus', 'features'
  43. 'include_in_backups', 'default_user', 'qrexec_timeout',
  44. 'autostart', 'pci_strictreset', 'debug',
  45. 'internal'):
  46. if not hasattr(vm, prop):
  47. continue
  48. self.assertEquals(
  49. getattr(vm, prop), getattr(restored_vm, prop),
  50. "VM {} - property {} not properly restored".format(
  51. vm.name, prop))
  52. for prop in ('netvm', 'template', 'label'):
  53. if not hasattr(vm, prop):
  54. continue
  55. orig_value = getattr(vm, prop)
  56. restored_value = getattr(restored_vm, prop)
  57. if orig_value and restored_value:
  58. self.assertEquals(orig_value.name, restored_value.name,
  59. "VM {} - property {} not properly restored".format(
  60. vm.name, prop))
  61. else:
  62. self.assertEquals(orig_value, restored_value,
  63. "VM {} - property {} not properly restored".format(
  64. vm.name, prop))
  65. for dev_class in vm.devices.keys():
  66. for dev in vm.devices[dev_class]:
  67. self.assertIn(dev, restored_vm.devices[dev_class])
  68. # TODO: compare disk images
  69. self.remove_vms(reversed(vms))
  70. def test_001_compressed_backup(self):
  71. vms = self.create_backup_vms()
  72. self.make_backup(vms, compressed=True)
  73. self.remove_vms(reversed(vms))
  74. self.restore_backup()
  75. for vm in vms:
  76. self.assertIn(vm.name, self.app.domains)
  77. def test_002_encrypted_backup(self):
  78. vms = self.create_backup_vms()
  79. self.make_backup(vms, encrypted=True)
  80. self.remove_vms(reversed(vms))
  81. self.restore_backup()
  82. for vm in vms:
  83. self.assertIn(vm.name, self.app.domains)
  84. def test_003_compressed_encrypted_backup(self):
  85. vms = self.create_backup_vms()
  86. self.make_backup(vms, compressed=True, encrypted=True)
  87. self.remove_vms(reversed(vms))
  88. self.restore_backup()
  89. for vm in vms:
  90. self.assertIn(vm.name, self.app.domains)
  91. def test_004_sparse_multipart(self):
  92. vms = []
  93. vmname = self.make_vm_name('testhvm2')
  94. if self.verbose:
  95. print >>sys.stderr, "-> Creating %s" % vmname
  96. hvmtemplate = self.app.add_new_vm(
  97. qubes.vm.templatevm.TemplateVM, name=vmname, hvm=True, label='red')
  98. hvmtemplate.create_on_disk()
  99. self.fill_image(
  100. os.path.join(hvmtemplate.dir_path, '00file'),
  101. 195 * 1024 * 1024 - 4096 * 3)
  102. self.fill_image(hvmtemplate.volumes['private'].path,
  103. 195 * 1024 * 1024 - 4096 * 3)
  104. self.fill_image(hvmtemplate.volumes['root'].path, 1024 * 1024 * 1024,
  105. sparse=True)
  106. vms.append(hvmtemplate)
  107. self.app.save()
  108. self.make_backup(vms)
  109. self.remove_vms(reversed(vms))
  110. self.restore_backup()
  111. for vm in vms:
  112. self.assertIn(vm.name, self.app.domains)
  113. # TODO check vm.backup_timestamp
  114. def test_005_compressed_custom(self):
  115. vms = self.create_backup_vms()
  116. self.make_backup(vms, compression_filter="bzip2")
  117. self.remove_vms(reversed(vms))
  118. self.restore_backup()
  119. for vm in vms:
  120. self.assertIn(vm.name, self.app.domains)
  121. def test_100_backup_dom0_no_restore(self):
  122. # do not write it into dom0 home itself...
  123. os.mkdir('/var/tmp/test-backup')
  124. self.backupdir = '/var/tmp/test-backup'
  125. self.make_backup([self.app.domains[0]])
  126. # TODO: think of some safe way to test restore...
  127. def test_200_restore_over_existing_directory(self):
  128. """
  129. Regression test for #1386
  130. :return:
  131. """
  132. vms = self.create_backup_vms()
  133. self.make_backup(vms)
  134. self.remove_vms(reversed(vms))
  135. test_dir = vms[0].dir_path
  136. os.mkdir(test_dir)
  137. with open(os.path.join(test_dir, 'some-file.txt'), 'w') as f:
  138. f.write('test file\n')
  139. self.restore_backup(
  140. expect_errors=[
  141. '*** Directory {} already exists! It has been moved'.format(
  142. test_dir)
  143. ])
  144. def test_210_auto_rename(self):
  145. """
  146. Test for #869
  147. :return:
  148. """
  149. vms = self.create_backup_vms()
  150. self.make_backup(vms)
  151. self.restore_backup(options={
  152. 'rename_conflicting': True
  153. })
  154. for vm in vms:
  155. with self.assertNotRaises(
  156. (qubes.exc.QubesVMNotFoundError, KeyError)):
  157. restored_vm = self.app.domains[vm.name + '1']
  158. if vm.netvm and not vm.property_is_default('netvm'):
  159. self.assertEqual(restored_vm.netvm.name, vm.netvm.name + '1')
  160. class TC_10_BackupVMMixin(qubes.tests.BackupTestsMixin):
  161. def setUp(self):
  162. super(TC_10_BackupVMMixin, self).setUp()
  163. self.backupvm = self.app.add_new_vm(
  164. qubes.vm.appvm.AppVM,
  165. label='red',
  166. name=self.make_vm_name('backupvm'),
  167. template=self.template
  168. )
  169. self.backupvm.create_on_disk()
  170. def test_100_send_to_vm_file_with_spaces(self):
  171. vms = self.create_backup_vms()
  172. self.backupvm.start()
  173. self.backupvm.run("mkdir '/var/tmp/backup directory'", wait=True)
  174. self.make_backup(vms, target_vm=self.backupvm,
  175. compressed=True, encrypted=True,
  176. target='/var/tmp/backup directory')
  177. self.remove_vms(reversed(vms))
  178. p = self.backupvm.run("ls /var/tmp/backup*/qubes-backup*",
  179. passio_popen=True)
  180. (backup_path, _) = p.communicate()
  181. backup_path = backup_path.strip()
  182. self.restore_backup(source=backup_path,
  183. appvm=self.backupvm)
  184. def test_110_send_to_vm_command(self):
  185. vms = self.create_backup_vms()
  186. self.backupvm.start()
  187. self.make_backup(vms, target_vm=self.backupvm,
  188. compressed=True, encrypted=True,
  189. target='dd of=/var/tmp/backup-test')
  190. self.remove_vms(reversed(vms))
  191. self.restore_backup(source='dd if=/var/tmp/backup-test',
  192. appvm=self.backupvm)
  193. def test_110_send_to_vm_no_space(self):
  194. """
  195. Check whether backup properly report failure when no enough space is
  196. available
  197. :return:
  198. """
  199. vms = self.create_backup_vms()
  200. self.backupvm.start()
  201. retcode = self.backupvm.run(
  202. # Debian 7 has too old losetup to handle loop-control device
  203. "mknod /dev/loop0 b 7 0;"
  204. "truncate -s 50M /home/user/backup.img && "
  205. "mkfs.ext4 -F /home/user/backup.img && "
  206. "mkdir /home/user/backup && "
  207. "mount /home/user/backup.img /home/user/backup -o loop &&"
  208. "chmod 777 /home/user/backup",
  209. user="root", wait=True)
  210. if retcode != 0:
  211. raise RuntimeError("Failed to prepare backup directory")
  212. with self.assertRaises(qubes.exc.QubesException):
  213. self.make_backup(vms, target_vm=self.backupvm,
  214. compressed=False, encrypted=True,
  215. target='/home/user/backup',
  216. expect_failure=True)
  217. def load_tests(loader, tests, pattern):
  218. try:
  219. app = qubes.Qubes()
  220. templates = [vm.name for vm in app.domains if
  221. isinstance(vm, qubes.vm.templatevm.TemplateVM)]
  222. except OSError:
  223. templates = []
  224. for template in templates:
  225. tests.addTests(loader.loadTestsFromTestCase(
  226. type(
  227. 'TC_10_BackupVM_' + template,
  228. (TC_10_BackupVMMixin, qubes.tests.QubesTestCase),
  229. {'template': template})))
  230. return tests