test_btrfs.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. from flexmock import flexmock
  2. from borgmatic.hooks.data_source import btrfs as module
  3. def test_get_filesystem_mount_points_parses_findmnt_output():
  4. flexmock(module.borgmatic.execute).should_receive(
  5. 'execute_command_and_capture_output'
  6. ).and_return(
  7. '/mnt0 /dev/loop0 btrfs rw,relatime,ssd,space_cache=v2,subvolid=5,subvol=/\n'
  8. '/mnt1 /dev/loop1 btrfs rw,relatime,ssd,space_cache=v2,subvolid=5,subvol=/\n'
  9. )
  10. assert module.get_filesystem_mount_points('findmnt') == ('/mnt0', '/mnt1')
  11. def test_get_subvolumes_for_filesystem_parses_subvolume_list_output():
  12. flexmock(module.borgmatic.execute).should_receive(
  13. 'execute_command_and_capture_output'
  14. ).and_return(
  15. 'ID 270 gen 107 top level 5 path subvol1\nID 272 gen 74 top level 5 path subvol2\n'
  16. )
  17. assert module.get_subvolumes_for_filesystem('btrfs', '/mnt') == ('/mnt/subvol1', '/mnt/subvol2')
  18. def test_get_subvolumes_for_filesystem_skips_empty_subvolume_paths():
  19. flexmock(module.borgmatic.execute).should_receive(
  20. 'execute_command_and_capture_output'
  21. ).and_return('\n \nID 272 gen 74 top level 5 path subvol2\n')
  22. assert module.get_subvolumes_for_filesystem('btrfs', '/mnt') == ('/mnt/subvol2',)
  23. def test_get_subvolumes_for_filesystem_skips_empty_filesystem_mount_points():
  24. flexmock(module.borgmatic.execute).should_receive(
  25. 'execute_command_and_capture_output'
  26. ).and_return(
  27. 'ID 270 gen 107 top level 5 path subvol1\nID 272 gen 74 top level 5 path subvol2\n'
  28. )
  29. assert module.get_subvolumes_for_filesystem('btrfs', ' ') == ()
  30. def test_get_subvolumes_collects_subvolumes_matching_source_directories_from_all_filesystems():
  31. flexmock(module).should_receive('get_filesystem_mount_points').and_return(('/mnt1', '/mnt2'))
  32. flexmock(module).should_receive('get_subvolumes_for_filesystem').with_args(
  33. 'btrfs', '/mnt1'
  34. ).and_return(('/one', '/two'))
  35. flexmock(module).should_receive('get_subvolumes_for_filesystem').with_args(
  36. 'btrfs', '/mnt2'
  37. ).and_return(('/three', '/four'))
  38. for path in ('/one', '/two', '/three', '/four'):
  39. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive('get_contained_directories').with_args(path).and_return((path,))
  40. assert module.get_subvolumes(
  41. 'btrfs', 'findmnt', source_directories=['/one', '/four', '/five', '/six', '/mnt2', '/mnt3']
  42. ) == (module.Subvolume('/one'), module.Subvolume('/mnt2'), module.Subvolume('/four'))
  43. def test_get_subvolumes_without_source_directories_collects_all_subvolumes_from_all_filesystems():
  44. flexmock(module).should_receive('get_filesystem_mount_points').and_return(('/mnt1', '/mnt2'))
  45. flexmock(module).should_receive('get_subvolumes_for_filesystem').with_args(
  46. 'btrfs', '/mnt1'
  47. ).and_return(('/one', '/two'))
  48. flexmock(module).should_receive('get_subvolumes_for_filesystem').with_args(
  49. 'btrfs', '/mnt2'
  50. ).and_return(('/three', '/four'))
  51. for path in ('/one', '/two', '/three', '/four'):
  52. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive('get_contained_directories').with_args(path).and_return((path,))
  53. assert module.get_subvolumes('btrfs', 'findmnt') == (
  54. module.Subvolume('/mnt1'),
  55. module.Subvolume('/one'),
  56. module.Subvolume('/two'),
  57. module.Subvolume('/mnt2'),
  58. module.Subvolume('/three'),
  59. module.Subvolume('/four'),
  60. )
  61. def test_dump_data_sources_snapshots_each_subvolume_and_updates_source_directories():
  62. source_directories = ['/foo', '/mnt/subvol1']
  63. config = {'btrfs': {}}
  64. flexmock(module).should_receive('get_subvolumes').and_return(('/mnt/subvol1', '/mnt/subvol2'))
  65. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  66. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  67. )
  68. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  69. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  70. )
  71. flexmock(module).should_receive('snapshot_subvolume').with_args(
  72. 'btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  73. ).once()
  74. flexmock(module).should_receive('snapshot_subvolume').with_args(
  75. 'btrfs', '/mnt/subvol2', '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  76. ).once()
  77. flexmock(module).should_receive('make_snapshot_exclude_path').with_args(
  78. '/mnt/subvol1'
  79. ).and_return('/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234')
  80. flexmock(module).should_receive('make_snapshot_exclude_path').with_args(
  81. '/mnt/subvol2'
  82. ).and_return('/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234')
  83. assert (
  84. module.dump_data_sources(
  85. hook_config=config['btrfs'],
  86. config=config,
  87. log_prefix='test',
  88. config_paths=('test.yaml',),
  89. borgmatic_runtime_directory='/run/borgmatic',
  90. source_directories=source_directories,
  91. dry_run=False,
  92. )
  93. == []
  94. )
  95. assert source_directories == [
  96. '/foo',
  97. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  98. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  99. ]
  100. assert config == {
  101. 'btrfs': {},
  102. 'exclude_patterns': [
  103. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  104. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  105. ],
  106. }
  107. def test_dump_data_sources_uses_custom_btrfs_command_in_commands():
  108. source_directories = ['/foo', '/mnt/subvol1']
  109. config = {'btrfs': {'btrfs_command': '/usr/local/bin/btrfs'}}
  110. flexmock(module).should_receive('get_subvolumes').and_return(('/mnt/subvol1',))
  111. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  112. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  113. )
  114. flexmock(module).should_receive('snapshot_subvolume').with_args(
  115. '/usr/local/bin/btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  116. ).once()
  117. flexmock(module).should_receive('make_snapshot_exclude_path').with_args(
  118. '/mnt/subvol1'
  119. ).and_return('/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234')
  120. assert (
  121. module.dump_data_sources(
  122. hook_config=config['btrfs'],
  123. config=config,
  124. log_prefix='test',
  125. config_paths=('test.yaml',),
  126. borgmatic_runtime_directory='/run/borgmatic',
  127. source_directories=source_directories,
  128. dry_run=False,
  129. )
  130. == []
  131. )
  132. assert source_directories == [
  133. '/foo',
  134. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  135. ]
  136. assert config == {
  137. 'btrfs': {
  138. 'btrfs_command': '/usr/local/bin/btrfs',
  139. },
  140. 'exclude_patterns': [
  141. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  142. ],
  143. }
  144. def test_dump_data_sources_uses_custom_findmnt_command_in_commands():
  145. source_directories = ['/foo', '/mnt/subvol1']
  146. config = {'btrfs': {'findmnt_command': '/usr/local/bin/findmnt'}}
  147. flexmock(module).should_receive('get_subvolumes').with_args(
  148. 'btrfs', '/usr/local/bin/findmnt', source_directories
  149. ).and_return(('/mnt/subvol1',)).once()
  150. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  151. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  152. )
  153. flexmock(module).should_receive('snapshot_subvolume').with_args(
  154. 'btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  155. ).once()
  156. flexmock(module).should_receive('make_snapshot_exclude_path').with_args(
  157. '/mnt/subvol1'
  158. ).and_return('/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234')
  159. assert (
  160. module.dump_data_sources(
  161. hook_config=config['btrfs'],
  162. config=config,
  163. log_prefix='test',
  164. config_paths=('test.yaml',),
  165. borgmatic_runtime_directory='/run/borgmatic',
  166. source_directories=source_directories,
  167. dry_run=False,
  168. )
  169. == []
  170. )
  171. assert source_directories == [
  172. '/foo',
  173. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  174. ]
  175. assert config == {
  176. 'btrfs': {
  177. 'findmnt_command': '/usr/local/bin/findmnt',
  178. },
  179. 'exclude_patterns': [
  180. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  181. ],
  182. }
  183. def test_dump_data_sources_with_dry_run_skips_snapshot_and_source_directories_update():
  184. source_directories = ['/foo', '/mnt/subvol1']
  185. config = {'btrfs': {}}
  186. flexmock(module).should_receive('get_subvolumes').and_return(('/mnt/subvol1',))
  187. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  188. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  189. )
  190. flexmock(module).should_receive('snapshot_subvolume').never()
  191. flexmock(module).should_receive('make_snapshot_exclude_path').never()
  192. assert (
  193. module.dump_data_sources(
  194. hook_config=config['btrfs'],
  195. config=config,
  196. log_prefix='test',
  197. config_paths=('test.yaml',),
  198. borgmatic_runtime_directory='/run/borgmatic',
  199. source_directories=source_directories,
  200. dry_run=True,
  201. )
  202. == []
  203. )
  204. assert source_directories == ['/foo', '/mnt/subvol1']
  205. assert config == {'btrfs': {}}
  206. def test_dump_data_sources_without_matching_subvolumes_skips_snapshot_and_source_directories_update():
  207. source_directories = ['/foo', '/mnt/subvol1']
  208. config = {'btrfs': {}}
  209. flexmock(module).should_receive('get_subvolumes').and_return(())
  210. flexmock(module).should_receive('make_snapshot_path').never()
  211. flexmock(module).should_receive('snapshot_subvolume').never()
  212. flexmock(module).should_receive('make_snapshot_exclude_path').never()
  213. assert (
  214. module.dump_data_sources(
  215. hook_config=config['btrfs'],
  216. config=config,
  217. log_prefix='test',
  218. config_paths=('test.yaml',),
  219. borgmatic_runtime_directory='/run/borgmatic',
  220. source_directories=source_directories,
  221. dry_run=False,
  222. )
  223. == []
  224. )
  225. assert source_directories == ['/foo', '/mnt/subvol1']
  226. assert config == {'btrfs': {}}
  227. def test_dump_data_sources_snapshots_adds_to_existing_exclude_patterns():
  228. source_directories = ['/foo', '/mnt/subvol1']
  229. config = {'btrfs': {}, 'exclude_patterns': ['/bar']}
  230. flexmock(module).should_receive('get_subvolumes').and_return(('/mnt/subvol1', '/mnt/subvol2'))
  231. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  232. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  233. )
  234. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  235. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  236. )
  237. flexmock(module).should_receive('snapshot_subvolume').with_args(
  238. 'btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  239. ).once()
  240. flexmock(module).should_receive('snapshot_subvolume').with_args(
  241. 'btrfs', '/mnt/subvol2', '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  242. ).once()
  243. flexmock(module).should_receive('make_snapshot_exclude_path').with_args(
  244. '/mnt/subvol1'
  245. ).and_return('/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234')
  246. flexmock(module).should_receive('make_snapshot_exclude_path').with_args(
  247. '/mnt/subvol2'
  248. ).and_return('/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234')
  249. assert (
  250. module.dump_data_sources(
  251. hook_config=config['btrfs'],
  252. config=config,
  253. log_prefix='test',
  254. config_paths=('test.yaml',),
  255. borgmatic_runtime_directory='/run/borgmatic',
  256. source_directories=source_directories,
  257. dry_run=False,
  258. )
  259. == []
  260. )
  261. assert source_directories == [
  262. '/foo',
  263. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  264. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  265. ]
  266. assert config == {
  267. 'btrfs': {},
  268. 'exclude_patterns': [
  269. '/bar',
  270. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  271. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  272. ],
  273. }
  274. def test_remove_data_source_dumps_deletes_snapshots():
  275. config = {'btrfs': {}}
  276. flexmock(module).should_receive('get_subvolumes').and_return(('/mnt/subvol1', '/mnt/subvol2'))
  277. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  278. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  279. )
  280. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  281. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  282. )
  283. flexmock(module.borgmatic.config.paths).should_receive(
  284. 'replace_temporary_subdirectory_with_glob'
  285. ).with_args(
  286. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  287. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  288. ).and_return(
  289. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  290. )
  291. flexmock(module.borgmatic.config.paths).should_receive(
  292. 'replace_temporary_subdirectory_with_glob'
  293. ).with_args(
  294. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  295. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  296. ).and_return(
  297. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  298. )
  299. flexmock(module.glob).should_receive('glob').with_args(
  300. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  301. ).and_return(
  302. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  303. )
  304. flexmock(module.glob).should_receive('glob').with_args(
  305. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  306. ).and_return(
  307. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  308. )
  309. flexmock(module.os.path).should_receive('isdir').with_args(
  310. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  311. ).and_return(True)
  312. flexmock(module.os.path).should_receive('isdir').with_args(
  313. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  314. ).and_return(True)
  315. flexmock(module.os.path).should_receive('isdir').with_args(
  316. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  317. ).and_return(True)
  318. flexmock(module.os.path).should_receive('isdir').with_args(
  319. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  320. ).and_return(False)
  321. flexmock(module).should_receive('delete_snapshot').with_args(
  322. 'btrfs', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  323. ).once()
  324. flexmock(module).should_receive('delete_snapshot').with_args(
  325. 'btrfs', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  326. ).once()
  327. flexmock(module).should_receive('delete_snapshot').with_args(
  328. 'btrfs', '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  329. ).once()
  330. flexmock(module).should_receive('delete_snapshot').with_args(
  331. 'btrfs', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  332. ).never()
  333. flexmock(module.shutil).should_receive('rmtree').with_args(
  334. '/mnt/subvol1/.borgmatic-1234'
  335. ).once()
  336. flexmock(module.shutil).should_receive('rmtree').with_args(
  337. '/mnt/subvol1/.borgmatic-5678'
  338. ).once()
  339. flexmock(module.shutil).should_receive('rmtree').with_args(
  340. '/mnt/subvol2/.borgmatic-1234'
  341. ).once()
  342. flexmock(module.shutil).should_receive('rmtree').with_args(
  343. '/mnt/subvol2/.borgmatic-5678'
  344. ).never()
  345. module.remove_data_source_dumps(
  346. hook_config=config['btrfs'],
  347. config=config,
  348. log_prefix='test',
  349. borgmatic_runtime_directory='/run/borgmatic',
  350. dry_run=False,
  351. )
  352. def test_remove_data_source_dumps_with_get_subvolumes_file_not_found_error_bails():
  353. config = {'btrfs': {}}
  354. flexmock(module).should_receive('get_subvolumes').and_raise(FileNotFoundError)
  355. flexmock(module).should_receive('make_snapshot_path').never()
  356. flexmock(module.borgmatic.config.paths).should_receive(
  357. 'replace_temporary_subdirectory_with_glob'
  358. ).never()
  359. flexmock(module).should_receive('delete_snapshot').never()
  360. flexmock(module.shutil).should_receive('rmtree').never()
  361. module.remove_data_source_dumps(
  362. hook_config=config['btrfs'],
  363. config=config,
  364. log_prefix='test',
  365. borgmatic_runtime_directory='/run/borgmatic',
  366. dry_run=False,
  367. )
  368. def test_remove_data_source_dumps_with_get_subvolumes_called_process_error_bails():
  369. config = {'btrfs': {}}
  370. flexmock(module).should_receive('get_subvolumes').and_raise(
  371. module.subprocess.CalledProcessError(1, 'command', 'error')
  372. )
  373. flexmock(module).should_receive('make_snapshot_path').never()
  374. flexmock(module.borgmatic.config.paths).should_receive(
  375. 'replace_temporary_subdirectory_with_glob'
  376. ).never()
  377. flexmock(module).should_receive('delete_snapshot').never()
  378. flexmock(module.shutil).should_receive('rmtree').never()
  379. module.remove_data_source_dumps(
  380. hook_config=config['btrfs'],
  381. config=config,
  382. log_prefix='test',
  383. borgmatic_runtime_directory='/run/borgmatic',
  384. dry_run=False,
  385. )
  386. def test_remove_data_source_dumps_with_dry_run_skips_deletes():
  387. config = {'btrfs': {}}
  388. flexmock(module).should_receive('get_subvolumes').and_return(('/mnt/subvol1', '/mnt/subvol2'))
  389. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  390. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  391. )
  392. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  393. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  394. )
  395. flexmock(module.borgmatic.config.paths).should_receive(
  396. 'replace_temporary_subdirectory_with_glob'
  397. ).with_args(
  398. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  399. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  400. ).and_return(
  401. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  402. )
  403. flexmock(module.borgmatic.config.paths).should_receive(
  404. 'replace_temporary_subdirectory_with_glob'
  405. ).with_args(
  406. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  407. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  408. ).and_return(
  409. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  410. )
  411. flexmock(module.glob).should_receive('glob').with_args(
  412. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  413. ).and_return(
  414. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  415. )
  416. flexmock(module.glob).should_receive('glob').with_args(
  417. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  418. ).and_return(
  419. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  420. )
  421. flexmock(module.os.path).should_receive('isdir').with_args(
  422. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  423. ).and_return(True)
  424. flexmock(module.os.path).should_receive('isdir').with_args(
  425. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  426. ).and_return(True)
  427. flexmock(module.os.path).should_receive('isdir').with_args(
  428. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  429. ).and_return(True)
  430. flexmock(module.os.path).should_receive('isdir').with_args(
  431. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  432. ).and_return(False)
  433. flexmock(module).should_receive('delete_snapshot').never()
  434. flexmock(module.shutil).should_receive('rmtree').never()
  435. module.remove_data_source_dumps(
  436. hook_config=config['btrfs'],
  437. config=config,
  438. log_prefix='test',
  439. borgmatic_runtime_directory='/run/borgmatic',
  440. dry_run=True,
  441. )
  442. def test_remove_data_source_dumps_without_subvolumes_skips_deletes():
  443. config = {'btrfs': {}}
  444. flexmock(module).should_receive('get_subvolumes').and_return(())
  445. flexmock(module).should_receive('make_snapshot_path').never()
  446. flexmock(module.borgmatic.config.paths).should_receive(
  447. 'replace_temporary_subdirectory_with_glob'
  448. ).never()
  449. flexmock(module).should_receive('delete_snapshot').never()
  450. flexmock(module.shutil).should_receive('rmtree').never()
  451. module.remove_data_source_dumps(
  452. hook_config=config['btrfs'],
  453. config=config,
  454. log_prefix='test',
  455. borgmatic_runtime_directory='/run/borgmatic',
  456. dry_run=False,
  457. )
  458. def test_remove_data_source_without_snapshots_skips_deletes():
  459. config = {'btrfs': {}}
  460. flexmock(module).should_receive('get_subvolumes').and_return(('/mnt/subvol1', '/mnt/subvol2'))
  461. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  462. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  463. )
  464. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  465. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  466. )
  467. flexmock(module.borgmatic.config.paths).should_receive(
  468. 'replace_temporary_subdirectory_with_glob'
  469. ).with_args(
  470. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  471. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  472. ).and_return(
  473. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  474. )
  475. flexmock(module.borgmatic.config.paths).should_receive(
  476. 'replace_temporary_subdirectory_with_glob'
  477. ).with_args(
  478. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  479. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  480. ).and_return(
  481. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  482. )
  483. flexmock(module.glob).should_receive('glob').and_return(())
  484. flexmock(module.os.path).should_receive('isdir').never()
  485. flexmock(module).should_receive('delete_snapshot').never()
  486. flexmock(module.shutil).should_receive('rmtree').never()
  487. module.remove_data_source_dumps(
  488. hook_config=config['btrfs'],
  489. config=config,
  490. log_prefix='test',
  491. borgmatic_runtime_directory='/run/borgmatic',
  492. dry_run=False,
  493. )
  494. def test_remove_data_source_dumps_with_delete_snapshot_file_not_found_error_bails():
  495. config = {'btrfs': {}}
  496. flexmock(module).should_receive('get_subvolumes').and_return(('/mnt/subvol1', '/mnt/subvol2'))
  497. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  498. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  499. )
  500. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  501. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  502. )
  503. flexmock(module.borgmatic.config.paths).should_receive(
  504. 'replace_temporary_subdirectory_with_glob'
  505. ).with_args(
  506. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  507. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  508. ).and_return(
  509. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  510. )
  511. flexmock(module.borgmatic.config.paths).should_receive(
  512. 'replace_temporary_subdirectory_with_glob'
  513. ).with_args(
  514. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  515. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  516. ).and_return(
  517. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  518. )
  519. flexmock(module.glob).should_receive('glob').with_args(
  520. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  521. ).and_return(
  522. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  523. )
  524. flexmock(module.glob).should_receive('glob').with_args(
  525. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  526. ).and_return(
  527. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  528. )
  529. flexmock(module.os.path).should_receive('isdir').with_args(
  530. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  531. ).and_return(True)
  532. flexmock(module.os.path).should_receive('isdir').with_args(
  533. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  534. ).and_return(True)
  535. flexmock(module.os.path).should_receive('isdir').with_args(
  536. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  537. ).and_return(True)
  538. flexmock(module.os.path).should_receive('isdir').with_args(
  539. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  540. ).and_return(False)
  541. flexmock(module).should_receive('delete_snapshot').and_raise(FileNotFoundError)
  542. flexmock(module.shutil).should_receive('rmtree').never()
  543. module.remove_data_source_dumps(
  544. hook_config=config['btrfs'],
  545. config=config,
  546. log_prefix='test',
  547. borgmatic_runtime_directory='/run/borgmatic',
  548. dry_run=False,
  549. )
  550. def test_remove_data_source_dumps_with_delete_snapshot_called_process_error_bails():
  551. config = {'btrfs': {}}
  552. flexmock(module).should_receive('get_subvolumes').and_return(('/mnt/subvol1', '/mnt/subvol2'))
  553. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  554. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  555. )
  556. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  557. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  558. )
  559. flexmock(module.borgmatic.config.paths).should_receive(
  560. 'replace_temporary_subdirectory_with_glob'
  561. ).with_args(
  562. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  563. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  564. ).and_return(
  565. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  566. )
  567. flexmock(module.borgmatic.config.paths).should_receive(
  568. 'replace_temporary_subdirectory_with_glob'
  569. ).with_args(
  570. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  571. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  572. ).and_return(
  573. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  574. )
  575. flexmock(module.glob).should_receive('glob').with_args(
  576. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  577. ).and_return(
  578. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  579. )
  580. flexmock(module.glob).should_receive('glob').with_args(
  581. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  582. ).and_return(
  583. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  584. )
  585. flexmock(module.os.path).should_receive('isdir').with_args(
  586. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  587. ).and_return(True)
  588. flexmock(module.os.path).should_receive('isdir').with_args(
  589. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  590. ).and_return(True)
  591. flexmock(module.os.path).should_receive('isdir').with_args(
  592. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  593. ).and_return(True)
  594. flexmock(module.os.path).should_receive('isdir').with_args(
  595. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  596. ).and_return(False)
  597. flexmock(module).should_receive('delete_snapshot').and_raise(
  598. module.subprocess.CalledProcessError(1, 'command', 'error')
  599. )
  600. flexmock(module.shutil).should_receive('rmtree').never()
  601. module.remove_data_source_dumps(
  602. hook_config=config['btrfs'],
  603. config=config,
  604. log_prefix='test',
  605. borgmatic_runtime_directory='/run/borgmatic',
  606. dry_run=False,
  607. )