backup.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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. from qubes.qubes import QubesException, QubesTemplateVm
  28. import qubes.tests
  29. class TC_00_Backup(qubes.tests.BackupTestsMixin, qubes.tests.QubesTestCase):
  30. def test_000_basic_backup(self):
  31. vms = self.create_backup_vms()
  32. self.make_backup(vms)
  33. self.remove_vms(vms)
  34. self.restore_backup()
  35. self.remove_vms(vms)
  36. def test_001_compressed_backup(self):
  37. vms = self.create_backup_vms()
  38. self.make_backup(vms, do_kwargs={'compressed': True})
  39. self.remove_vms(vms)
  40. self.restore_backup()
  41. self.remove_vms(vms)
  42. def test_002_encrypted_backup(self):
  43. vms = self.create_backup_vms()
  44. self.make_backup(vms, do_kwargs={'encrypted': True})
  45. self.remove_vms(vms)
  46. self.restore_backup()
  47. self.remove_vms(vms)
  48. def test_003_compressed_encrypted_backup(self):
  49. vms = self.create_backup_vms()
  50. self.make_backup(vms,
  51. do_kwargs={
  52. 'compressed': True,
  53. 'encrypted': True})
  54. self.remove_vms(vms)
  55. self.restore_backup()
  56. self.remove_vms(vms)
  57. def test_004_sparse_multipart(self):
  58. vms = []
  59. vmname = self.make_vm_name('testhvm2')
  60. if self.verbose:
  61. print >>sys.stderr, "-> Creating %s" % vmname
  62. hvmtemplate = self.qc.add_new_vm("QubesTemplateHVm", name=vmname)
  63. hvmtemplate.create_on_disk(verbose=self.verbose)
  64. self.fill_image(os.path.join(hvmtemplate.dir_path, '00file'),
  65. 195*1024*1024-4096*3)
  66. self.fill_image(hvmtemplate.private_img, 195*1024*1024-4096*3)
  67. self.fill_image(hvmtemplate.root_img, 1024*1024*1024, sparse=True)
  68. vms.append(hvmtemplate)
  69. self.qc.save()
  70. self.make_backup(vms)
  71. self.remove_vms(vms)
  72. self.restore_backup()
  73. self.remove_vms(vms)
  74. def test_005_compressed_custom(self):
  75. vms = self.create_backup_vms()
  76. self.make_backup(vms, do_kwargs={'compressed': "bzip2"})
  77. self.remove_vms(vms)
  78. self.restore_backup()
  79. self.remove_vms(vms)
  80. def test_100_backup_dom0_no_restore(self):
  81. self.make_backup([self.qc[0]])
  82. # TODO: think of some safe way to test restore...
  83. def test_200_restore_over_existing_directory(self):
  84. """
  85. Regression test for #1386
  86. :return:
  87. """
  88. vms = self.create_backup_vms()
  89. self.make_backup(vms)
  90. self.remove_vms(vms)
  91. test_dir = vms[0].dir_path
  92. os.mkdir(test_dir)
  93. with open(os.path.join(test_dir, 'some-file.txt'), 'w') as f:
  94. f.write('test file\n')
  95. self.restore_backup(
  96. expect_errors=[
  97. '*** Directory {} already exists! It has been moved'.format(
  98. test_dir)
  99. ])
  100. self.remove_vms(vms)
  101. def test_210_auto_rename(self):
  102. """
  103. Test for #869
  104. :return:
  105. """
  106. vms = self.create_backup_vms()
  107. self.make_backup(vms)
  108. self.restore_backup(options={
  109. 'rename-conflicting': True
  110. })
  111. for vm in vms:
  112. self.assertIsNotNone(self.qc.get_vm_by_name(vm.name+'1'))
  113. restored_vm = self.qc.get_vm_by_name(vm.name+'1')
  114. if vm.netvm and not vm.uses_default_netvm:
  115. self.assertEqual(restored_vm.netvm.name, vm.netvm.name+'1')
  116. self.remove_vms(vms)
  117. class TC_10_BackupVMMixin(qubes.tests.BackupTestsMixin):
  118. def setUp(self):
  119. super(TC_10_BackupVMMixin, self).setUp()
  120. self.backupvm = self.qc.add_new_vm(
  121. "QubesAppVm",
  122. name=self.make_vm_name('backupvm'),
  123. template=self.qc.get_vm_by_name(self.template)
  124. )
  125. self.backupvm.create_on_disk(verbose=self.verbose)
  126. def test_100_send_to_vm_file_with_spaces(self):
  127. vms = self.create_backup_vms()
  128. self.backupvm.start()
  129. self.backupvm.run("mkdir '/var/tmp/backup directory'", wait=True)
  130. self.make_backup(vms,
  131. do_kwargs={
  132. 'appvm': self.backupvm,
  133. 'compressed': True,
  134. 'encrypted': True},
  135. target='/var/tmp/backup directory')
  136. self.remove_vms(vms)
  137. p = self.backupvm.run("ls /var/tmp/backup*/qubes-backup*",
  138. passio_popen=True)
  139. (backup_path, _) = p.communicate()
  140. backup_path = backup_path.strip()
  141. self.restore_backup(source=backup_path,
  142. appvm=self.backupvm)
  143. self.remove_vms(vms)
  144. def test_110_send_to_vm_command(self):
  145. vms = self.create_backup_vms()
  146. self.backupvm.start()
  147. self.make_backup(vms,
  148. do_kwargs={
  149. 'appvm': self.backupvm,
  150. 'compressed': True,
  151. 'encrypted': True},
  152. target='dd of=/var/tmp/backup-test')
  153. self.remove_vms(vms)
  154. self.restore_backup(source='dd if=/var/tmp/backup-test',
  155. appvm=self.backupvm)
  156. self.remove_vms(vms)
  157. def test_110_send_to_vm_no_space(self):
  158. """
  159. Check whether backup properly report failure when no enough space is
  160. available
  161. :return:
  162. """
  163. vms = self.create_backup_vms()
  164. self.backupvm.start()
  165. retcode = self.backupvm.run(
  166. "truncate -s 50M /home/user/backup.img && "
  167. "mkfs.ext4 -F /home/user/backup.img && "
  168. "mkdir /home/user/backup && "
  169. "mount /home/user/backup.img /home/user/backup -o loop &&"
  170. "chmod 777 /home/user/backup",
  171. user="root", wait=True)
  172. if retcode != 0:
  173. raise RuntimeError("Failed to prepare backup directory")
  174. with self.assertRaises(QubesException):
  175. self.make_backup(vms,
  176. do_kwargs={
  177. 'appvm': self.backupvm,
  178. 'compressed': False,
  179. 'encrypted': True},
  180. target='/home/user/backup',
  181. expect_failure=True)
  182. self.qc.lock_db_for_writing()
  183. self.qc.load()
  184. self.remove_vms(vms)
  185. def load_tests(loader, tests, pattern):
  186. try:
  187. qc = qubes.qubes.QubesVmCollection()
  188. qc.lock_db_for_reading()
  189. qc.load()
  190. qc.unlock_db()
  191. templates = [vm.name for vm in qc.values() if
  192. isinstance(vm, QubesTemplateVm)]
  193. except OSError:
  194. templates = []
  195. for template in templates:
  196. tests.addTests(loader.loadTestsFromTestCase(
  197. type(
  198. 'TC_10_BackupVM_' + template,
  199. (TC_10_BackupVMMixin, qubes.tests.QubesTestCase),
  200. {'template': template})))
  201. return tests