test_btrfs.py 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111
  1. import pytest
  2. from flexmock import flexmock
  3. from borgmatic.borg.pattern import Pattern, Pattern_source, Pattern_style, Pattern_type
  4. from borgmatic.hooks.data_source import btrfs as module
  5. def test_get_contained_subvolume_paths_parses_btrfs_output():
  6. flexmock(module.borgmatic.execute).should_receive(
  7. 'execute_command_and_capture_output',
  8. ).with_args(('btrfs', 'subvolume', 'list', '/mnt0'), close_fds=True).and_return(
  9. 'ID 256 gen 28 top level 5 path @sub\nID 258 gen 17 top level 5 path snap\n\n',
  10. )
  11. assert module.get_contained_subvolume_paths('btrfs', '/mnt0') == (
  12. '/mnt0',
  13. '/mnt0/@sub',
  14. '/mnt0/snap',
  15. )
  16. def test_get_contained_subvolume_paths_swallows_called_process_error():
  17. flexmock(module.borgmatic.execute).should_receive(
  18. 'execute_command_and_capture_output',
  19. ).with_args(('btrfs', 'subvolume', 'list', '/mnt0'), close_fds=True).and_raise(
  20. module.subprocess.CalledProcessError(1, 'btrfs'),
  21. )
  22. assert module.get_contained_subvolume_paths('btrfs', '/mnt0') == ()
  23. def test_get_all_subvolume_paths_parses_findmnt_output():
  24. flexmock(module.borgmatic.execute).should_receive(
  25. 'execute_command_and_capture_output',
  26. ).and_return(
  27. '''{
  28. "filesystems": [
  29. {
  30. "target": "/mnt0",
  31. "source": "/dev/loop0",
  32. "fstype": "btrfs",
  33. "options": "rw,relatime,ssd,space_cache=v2,subvolid=5,subvol=/"
  34. },
  35. {
  36. "target": "/mnt1",
  37. "source": "/dev/loop0",
  38. "fstype": "btrfs",
  39. "options": "rw,relatime,ssd,space_cache=v2,subvolid=5,subvol=/"
  40. },
  41. {
  42. "target": "/mnt2",
  43. "source": "/dev/loop0",
  44. "fstype": "btrfs",
  45. "options": "rw,relatime,ssd,space_cache=v2,subvolid=256,subvol=/"
  46. }
  47. ]
  48. }
  49. ''',
  50. )
  51. flexmock(module).should_receive('get_contained_subvolume_paths').with_args(
  52. 'btrfs',
  53. '/mnt0',
  54. ).and_return(('/mnt0',))
  55. flexmock(module).should_receive('get_contained_subvolume_paths').with_args(
  56. 'btrfs',
  57. '/mnt1',
  58. ).and_return(('/mnt1', '/mnt1/sub'))
  59. flexmock(module).should_receive('get_contained_subvolume_paths').with_args(
  60. 'btrfs',
  61. '/mnt2',
  62. ).never()
  63. assert module.get_all_subvolume_paths('btrfs', 'findmnt') == (
  64. '/mnt0',
  65. '/mnt1',
  66. '/mnt1/sub',
  67. '/mnt2',
  68. )
  69. def test_get_all_subvolume_paths_with_invalid_findmnt_json_errors():
  70. flexmock(module.borgmatic.execute).should_receive(
  71. 'execute_command_and_capture_output',
  72. ).and_return('{')
  73. flexmock(module).should_receive('get_contained_subvolume_paths').never()
  74. with pytest.raises(ValueError):
  75. module.get_all_subvolume_paths('btrfs', 'findmnt')
  76. def test_get_all_subvolume_paths_with_findmnt_json_missing_filesystems_errors():
  77. flexmock(module.borgmatic.execute).should_receive(
  78. 'execute_command_and_capture_output',
  79. ).and_return('{"wtf": "something is wrong here"}')
  80. flexmock(module).should_receive('get_contained_subvolume_paths').never()
  81. with pytest.raises(ValueError):
  82. module.get_all_subvolume_paths('btrfs', 'findmnt')
  83. def test_get_subvolume_property_with_invalid_btrfs_output_errors():
  84. flexmock(module.borgmatic.execute).should_receive(
  85. 'execute_command_and_capture_output',
  86. ).and_return('invalid')
  87. with pytest.raises(ValueError):
  88. module.get_subvolume_property('btrfs', '/foo', 'ro')
  89. def test_get_subvolume_property_with_true_output_returns_true_bool():
  90. flexmock(module.borgmatic.execute).should_receive(
  91. 'execute_command_and_capture_output',
  92. ).and_return('ro=true')
  93. assert module.get_subvolume_property('btrfs', '/foo', 'ro') is True
  94. def test_get_subvolume_property_with_false_output_returns_false_bool():
  95. flexmock(module.borgmatic.execute).should_receive(
  96. 'execute_command_and_capture_output',
  97. ).and_return('ro=false')
  98. assert module.get_subvolume_property('btrfs', '/foo', 'ro') is False
  99. def test_get_subvolume_property_passes_through_general_value():
  100. flexmock(module.borgmatic.execute).should_receive(
  101. 'execute_command_and_capture_output',
  102. ).and_return('thing=value')
  103. assert module.get_subvolume_property('btrfs', '/foo', 'thing') == 'value'
  104. def test_omit_read_only_subvolume_paths_filters_out_read_only_subvolumes():
  105. flexmock(module).should_receive('get_subvolume_property').with_args(
  106. 'btrfs',
  107. '/foo',
  108. 'ro',
  109. ).and_return(False)
  110. flexmock(module).should_receive('get_subvolume_property').with_args(
  111. 'btrfs',
  112. '/bar',
  113. 'ro',
  114. ).and_return(True)
  115. flexmock(module).should_receive('get_subvolume_property').with_args(
  116. 'btrfs',
  117. '/baz',
  118. 'ro',
  119. ).and_return(False)
  120. assert module.omit_read_only_subvolume_paths('btrfs', ('/foo', '/bar', '/baz')) == (
  121. '/foo',
  122. '/baz',
  123. )
  124. def test_omit_read_only_subvolume_paths_filters_out_erroring_subvolumes():
  125. flexmock(module).should_receive('get_subvolume_property').with_args(
  126. 'btrfs',
  127. '/foo',
  128. 'ro',
  129. ).and_raise(module.subprocess.CalledProcessError(1, 'btrfs'))
  130. flexmock(module).should_receive('get_subvolume_property').with_args(
  131. 'btrfs',
  132. '/bar',
  133. 'ro',
  134. ).and_return(True)
  135. flexmock(module).should_receive('get_subvolume_property').with_args(
  136. 'btrfs',
  137. '/baz',
  138. 'ro',
  139. ).and_return(False)
  140. assert module.omit_read_only_subvolume_paths('btrfs', ('/foo', '/bar', '/baz')) == ('/baz',)
  141. def test_get_subvolumes_collects_subvolumes_matching_patterns():
  142. flexmock(module).should_receive('get_all_subvolume_paths').and_return(('/mnt1', '/mnt2'))
  143. flexmock(module).should_receive('omit_read_only_subvolume_paths').and_return(('/mnt1', '/mnt2'))
  144. contained_pattern = Pattern(
  145. '/mnt1',
  146. type=Pattern_type.ROOT,
  147. source=Pattern_source.CONFIG,
  148. )
  149. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  150. 'get_contained_patterns',
  151. ).with_args('/mnt1', object).and_return((contained_pattern,))
  152. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  153. 'get_contained_patterns',
  154. ).with_args('/mnt2', object).and_return(())
  155. assert module.get_subvolumes(
  156. 'btrfs',
  157. 'findmnt',
  158. patterns=[
  159. Pattern('/mnt1'),
  160. Pattern('/mnt3'),
  161. ],
  162. ) == (module.Subvolume('/mnt1', contained_patterns=(contained_pattern,)),)
  163. def test_get_subvolumes_skips_non_root_patterns():
  164. flexmock(module).should_receive('get_all_subvolume_paths').and_return(('/mnt1', '/mnt2'))
  165. flexmock(module).should_receive('omit_read_only_subvolume_paths').and_return(('/mnt1', '/mnt2'))
  166. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  167. 'get_contained_patterns',
  168. ).with_args('/mnt1', object).and_return(
  169. (
  170. Pattern(
  171. '/mnt1',
  172. type=Pattern_type.EXCLUDE,
  173. source=Pattern_source.CONFIG,
  174. ),
  175. ),
  176. )
  177. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  178. 'get_contained_patterns',
  179. ).with_args('/mnt2', object).and_return(())
  180. assert (
  181. module.get_subvolumes(
  182. 'btrfs',
  183. 'findmnt',
  184. patterns=[
  185. Pattern('/mnt1'),
  186. Pattern('/mnt3'),
  187. ],
  188. )
  189. == ()
  190. )
  191. def test_get_subvolumes_skips_non_config_patterns():
  192. flexmock(module).should_receive('get_all_subvolume_paths').and_return(('/mnt1', '/mnt2'))
  193. flexmock(module).should_receive('omit_read_only_subvolume_paths').and_return(('/mnt1', '/mnt2'))
  194. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  195. 'get_contained_patterns',
  196. ).with_args('/mnt1', object).and_return(
  197. (
  198. Pattern(
  199. '/mnt1',
  200. type=Pattern_type.ROOT,
  201. source=Pattern_source.HOOK,
  202. ),
  203. ),
  204. )
  205. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  206. 'get_contained_patterns',
  207. ).with_args('/mnt2', object).and_return(())
  208. assert (
  209. module.get_subvolumes(
  210. 'btrfs',
  211. 'findmnt',
  212. patterns=[
  213. Pattern('/mnt1'),
  214. Pattern('/mnt3'),
  215. ],
  216. )
  217. == ()
  218. )
  219. def test_get_subvolumes_without_patterns_collects_all_subvolumes():
  220. flexmock(module).should_receive('get_all_subvolume_paths').and_return(('/mnt1', '/mnt2'))
  221. flexmock(module).should_receive('omit_read_only_subvolume_paths').and_return(('/mnt1', '/mnt2'))
  222. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  223. 'get_contained_patterns',
  224. ).with_args('/mnt1', object).and_return((Pattern('/mnt1'),))
  225. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  226. 'get_contained_patterns',
  227. ).with_args('/mnt2', object).and_return((Pattern('/mnt2'),))
  228. assert module.get_subvolumes('btrfs', 'findmnt') == (
  229. module.Subvolume('/mnt1', contained_patterns=(Pattern('/mnt1'),)),
  230. module.Subvolume('/mnt2', contained_patterns=(Pattern('/mnt2'),)),
  231. )
  232. @pytest.mark.parametrize(
  233. 'subvolume_path,expected_snapshot_path',
  234. (
  235. ('/foo/bar', '/foo/bar/.borgmatic-snapshot-1234/foo/bar'),
  236. ('/', '/.borgmatic-snapshot-1234'),
  237. ),
  238. )
  239. def test_make_snapshot_path_includes_stripped_subvolume_path(
  240. subvolume_path,
  241. expected_snapshot_path,
  242. ):
  243. flexmock(module.os).should_receive('getpid').and_return(1234)
  244. assert module.make_snapshot_path(subvolume_path) == expected_snapshot_path
  245. @pytest.mark.parametrize(
  246. 'subvolume_path,pattern,expected_pattern',
  247. (
  248. (
  249. '/foo/bar',
  250. Pattern('/foo/bar/baz'),
  251. Pattern('/foo/bar/.borgmatic-snapshot-1234/./foo/bar/baz'),
  252. ),
  253. ('/foo/bar', Pattern('/foo/bar'), Pattern('/foo/bar/.borgmatic-snapshot-1234/./foo/bar')),
  254. (
  255. '/foo/bar',
  256. Pattern('^/foo/bar', Pattern_type.INCLUDE, Pattern_style.REGULAR_EXPRESSION),
  257. Pattern(
  258. '^/foo/bar/.borgmatic-snapshot-1234/./foo/bar',
  259. Pattern_type.INCLUDE,
  260. Pattern_style.REGULAR_EXPRESSION,
  261. ),
  262. ),
  263. (
  264. '/foo/bar',
  265. Pattern('/foo/bar', Pattern_type.INCLUDE, Pattern_style.REGULAR_EXPRESSION),
  266. Pattern(
  267. '/foo/bar/.borgmatic-snapshot-1234/./foo/bar',
  268. Pattern_type.INCLUDE,
  269. Pattern_style.REGULAR_EXPRESSION,
  270. ),
  271. ),
  272. ('/', Pattern('/foo'), Pattern('/.borgmatic-snapshot-1234/./foo')),
  273. ('/', Pattern('/'), Pattern('/.borgmatic-snapshot-1234/./')),
  274. (
  275. '/foo/bar',
  276. Pattern('/foo/bar/./baz'),
  277. Pattern('/foo/bar/.borgmatic-snapshot-1234/foo/bar/./baz'),
  278. ),
  279. ),
  280. )
  281. def test_make_borg_snapshot_pattern_includes_slashdot_hack_and_stripped_pattern_path(
  282. subvolume_path,
  283. pattern,
  284. expected_pattern,
  285. ):
  286. flexmock(module.os).should_receive('getpid').and_return(1234)
  287. assert module.make_borg_snapshot_pattern(subvolume_path, pattern) == expected_pattern
  288. def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns():
  289. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  290. config = {'btrfs': {}}
  291. flexmock(module).should_receive('get_subvolumes').and_return(
  292. (
  293. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  294. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  295. ),
  296. )
  297. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  298. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  299. )
  300. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  301. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  302. )
  303. flexmock(module).should_receive('snapshot_subvolume').with_args(
  304. 'btrfs',
  305. '/mnt/subvol1',
  306. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  307. ).once()
  308. flexmock(module).should_receive('snapshot_subvolume').with_args(
  309. 'btrfs',
  310. '/mnt/subvol2',
  311. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  312. ).once()
  313. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  314. '/mnt/subvol1',
  315. ).and_return(
  316. Pattern(
  317. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  318. Pattern_type.NO_RECURSE,
  319. Pattern_style.FNMATCH,
  320. ),
  321. )
  322. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  323. '/mnt/subvol2',
  324. ).and_return(
  325. Pattern(
  326. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  327. Pattern_type.NO_RECURSE,
  328. Pattern_style.FNMATCH,
  329. ),
  330. )
  331. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  332. '/mnt/subvol1',
  333. object,
  334. ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
  335. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  336. '/mnt/subvol2',
  337. object,
  338. ).and_return(Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'))
  339. assert (
  340. module.dump_data_sources(
  341. hook_config=config['btrfs'],
  342. config=config,
  343. config_paths=('test.yaml',),
  344. borgmatic_runtime_directory='/run/borgmatic',
  345. patterns=patterns,
  346. dry_run=False,
  347. )
  348. == []
  349. )
  350. assert patterns == [
  351. Pattern('/foo'),
  352. Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
  353. Pattern(
  354. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  355. Pattern_type.NO_RECURSE,
  356. Pattern_style.FNMATCH,
  357. ),
  358. Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'),
  359. Pattern(
  360. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  361. Pattern_type.NO_RECURSE,
  362. Pattern_style.FNMATCH,
  363. ),
  364. ]
  365. assert config == {
  366. 'btrfs': {},
  367. }
  368. def test_dump_data_sources_uses_custom_btrfs_command_in_commands():
  369. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  370. config = {'btrfs': {'btrfs_command': '/usr/local/bin/btrfs'}}
  371. flexmock(module).should_receive('get_subvolumes').and_return(
  372. (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),),
  373. )
  374. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  375. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  376. )
  377. flexmock(module).should_receive('snapshot_subvolume').with_args(
  378. '/usr/local/bin/btrfs',
  379. '/mnt/subvol1',
  380. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  381. ).once()
  382. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  383. '/mnt/subvol1',
  384. ).and_return(
  385. Pattern(
  386. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  387. Pattern_type.NO_RECURSE,
  388. Pattern_style.FNMATCH,
  389. ),
  390. )
  391. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  392. '/mnt/subvol1',
  393. object,
  394. ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
  395. assert (
  396. module.dump_data_sources(
  397. hook_config=config['btrfs'],
  398. config=config,
  399. config_paths=('test.yaml',),
  400. borgmatic_runtime_directory='/run/borgmatic',
  401. patterns=patterns,
  402. dry_run=False,
  403. )
  404. == []
  405. )
  406. assert patterns == [
  407. Pattern('/foo'),
  408. Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
  409. Pattern(
  410. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  411. Pattern_type.NO_RECURSE,
  412. Pattern_style.FNMATCH,
  413. ),
  414. ]
  415. assert config == {
  416. 'btrfs': {
  417. 'btrfs_command': '/usr/local/bin/btrfs',
  418. },
  419. }
  420. def test_dump_data_sources_uses_custom_findmnt_command_in_commands():
  421. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  422. config = {'btrfs': {'findmnt_command': '/usr/local/bin/findmnt'}}
  423. flexmock(module).should_receive('get_subvolumes').with_args(
  424. 'btrfs',
  425. '/usr/local/bin/findmnt',
  426. patterns,
  427. ).and_return(
  428. (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),),
  429. ).once()
  430. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  431. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  432. )
  433. flexmock(module).should_receive('snapshot_subvolume').with_args(
  434. 'btrfs',
  435. '/mnt/subvol1',
  436. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  437. ).once()
  438. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  439. '/mnt/subvol1',
  440. ).and_return(
  441. Pattern(
  442. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  443. Pattern_type.NO_RECURSE,
  444. Pattern_style.FNMATCH,
  445. ),
  446. )
  447. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  448. '/mnt/subvol1',
  449. object,
  450. ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
  451. assert (
  452. module.dump_data_sources(
  453. hook_config=config['btrfs'],
  454. config=config,
  455. config_paths=('test.yaml',),
  456. borgmatic_runtime_directory='/run/borgmatic',
  457. patterns=patterns,
  458. dry_run=False,
  459. )
  460. == []
  461. )
  462. assert patterns == [
  463. Pattern('/foo'),
  464. Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
  465. Pattern(
  466. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  467. Pattern_type.NO_RECURSE,
  468. Pattern_style.FNMATCH,
  469. ),
  470. ]
  471. assert config == {
  472. 'btrfs': {
  473. 'findmnt_command': '/usr/local/bin/findmnt',
  474. },
  475. }
  476. def test_dump_data_sources_with_dry_run_skips_snapshot_and_patterns_update():
  477. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  478. config = {'btrfs': {}}
  479. flexmock(module).should_receive('get_subvolumes').and_return(
  480. (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),),
  481. )
  482. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  483. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  484. )
  485. flexmock(module).should_receive('snapshot_subvolume').never()
  486. flexmock(module).should_receive('make_snapshot_exclude_pattern').never()
  487. assert (
  488. module.dump_data_sources(
  489. hook_config=config['btrfs'],
  490. config=config,
  491. config_paths=('test.yaml',),
  492. borgmatic_runtime_directory='/run/borgmatic',
  493. patterns=patterns,
  494. dry_run=True,
  495. )
  496. == []
  497. )
  498. assert patterns == [Pattern('/foo'), Pattern('/mnt/subvol1')]
  499. assert config == {'btrfs': {}}
  500. def test_dump_data_sources_without_matching_subvolumes_skips_snapshot_and_patterns_update():
  501. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  502. config = {'btrfs': {}}
  503. flexmock(module).should_receive('get_subvolumes').and_return(())
  504. flexmock(module).should_receive('make_snapshot_path').never()
  505. flexmock(module).should_receive('snapshot_subvolume').never()
  506. flexmock(module).should_receive('make_snapshot_exclude_pattern').never()
  507. assert (
  508. module.dump_data_sources(
  509. hook_config=config['btrfs'],
  510. config=config,
  511. config_paths=('test.yaml',),
  512. borgmatic_runtime_directory='/run/borgmatic',
  513. patterns=patterns,
  514. dry_run=False,
  515. )
  516. == []
  517. )
  518. assert patterns == [Pattern('/foo'), Pattern('/mnt/subvol1')]
  519. assert config == {'btrfs': {}}
  520. def test_dump_data_sources_snapshots_adds_to_existing_exclude_patterns():
  521. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  522. config = {'btrfs': {}, 'exclude_patterns': ['/bar']}
  523. flexmock(module).should_receive('get_subvolumes').and_return(
  524. (
  525. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  526. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  527. ),
  528. )
  529. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  530. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  531. )
  532. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  533. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  534. )
  535. flexmock(module).should_receive('snapshot_subvolume').with_args(
  536. 'btrfs',
  537. '/mnt/subvol1',
  538. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  539. ).once()
  540. flexmock(module).should_receive('snapshot_subvolume').with_args(
  541. 'btrfs',
  542. '/mnt/subvol2',
  543. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  544. ).once()
  545. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  546. '/mnt/subvol1',
  547. ).and_return(
  548. Pattern(
  549. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  550. Pattern_type.NO_RECURSE,
  551. Pattern_style.FNMATCH,
  552. ),
  553. )
  554. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  555. '/mnt/subvol2',
  556. ).and_return(
  557. Pattern(
  558. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  559. Pattern_type.NO_RECURSE,
  560. Pattern_style.FNMATCH,
  561. ),
  562. )
  563. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  564. '/mnt/subvol1',
  565. object,
  566. ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
  567. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  568. '/mnt/subvol2',
  569. object,
  570. ).and_return(Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'))
  571. assert (
  572. module.dump_data_sources(
  573. hook_config=config['btrfs'],
  574. config=config,
  575. config_paths=('test.yaml',),
  576. borgmatic_runtime_directory='/run/borgmatic',
  577. patterns=patterns,
  578. dry_run=False,
  579. )
  580. == []
  581. )
  582. assert patterns == [
  583. Pattern('/foo'),
  584. Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
  585. Pattern(
  586. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  587. Pattern_type.NO_RECURSE,
  588. Pattern_style.FNMATCH,
  589. ),
  590. Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'),
  591. Pattern(
  592. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  593. Pattern_type.NO_RECURSE,
  594. Pattern_style.FNMATCH,
  595. ),
  596. ]
  597. assert config == {
  598. 'btrfs': {},
  599. 'exclude_patterns': ['/bar'],
  600. }
  601. def test_remove_data_source_dumps_deletes_snapshots():
  602. config = {'btrfs': {}}
  603. flexmock(module).should_receive('get_subvolumes').and_return(
  604. (
  605. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  606. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  607. ),
  608. )
  609. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  610. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1',
  611. )
  612. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  613. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2',
  614. )
  615. flexmock(module.borgmatic.config.paths).should_receive(
  616. 'replace_temporary_subdirectory_with_glob',
  617. ).with_args(
  618. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  619. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  620. ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
  621. flexmock(module.borgmatic.config.paths).should_receive(
  622. 'replace_temporary_subdirectory_with_glob',
  623. ).with_args(
  624. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  625. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  626. ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
  627. flexmock(module.glob).should_receive('glob').with_args(
  628. '/mnt/subvol1/.borgmatic-*/mnt/subvol1',
  629. ).and_return(
  630. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'),
  631. )
  632. flexmock(module.glob).should_receive('glob').with_args(
  633. '/mnt/subvol2/.borgmatic-*/mnt/subvol2',
  634. ).and_return(
  635. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'),
  636. )
  637. flexmock(module.os.path).should_receive('isdir').with_args(
  638. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  639. ).and_return(True)
  640. flexmock(module.os.path).should_receive('isdir').with_args(
  641. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1',
  642. ).and_return(True)
  643. flexmock(module.os.path).should_receive('isdir').with_args(
  644. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  645. ).and_return(True)
  646. flexmock(module.os.path).should_receive('isdir').with_args(
  647. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2',
  648. ).and_return(False)
  649. flexmock(module).should_receive('delete_snapshot').with_args(
  650. 'btrfs',
  651. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  652. ).once()
  653. flexmock(module).should_receive('delete_snapshot').with_args(
  654. 'btrfs',
  655. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1',
  656. ).once()
  657. flexmock(module).should_receive('delete_snapshot').with_args(
  658. 'btrfs',
  659. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  660. ).once()
  661. flexmock(module).should_receive('delete_snapshot').with_args(
  662. 'btrfs',
  663. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2',
  664. ).never()
  665. flexmock(module.os.path).should_receive('isdir').with_args(
  666. '/mnt/subvol1/.borgmatic-1234',
  667. ).and_return(True)
  668. flexmock(module.os.path).should_receive('isdir').with_args(
  669. '/mnt/subvol1/.borgmatic-5678',
  670. ).and_return(True)
  671. flexmock(module.os.path).should_receive('isdir').with_args(
  672. '/mnt/subvol2/.borgmatic-1234',
  673. ).and_return(True)
  674. flexmock(module.os.path).should_receive('isdir').with_args(
  675. '/mnt/subvol2/.borgmatic-5678',
  676. ).and_return(True)
  677. flexmock(module.shutil).should_receive('rmtree').with_args(
  678. '/mnt/subvol1/.borgmatic-1234',
  679. ).once()
  680. flexmock(module.shutil).should_receive('rmtree').with_args(
  681. '/mnt/subvol1/.borgmatic-5678',
  682. ).once()
  683. flexmock(module.shutil).should_receive('rmtree').with_args(
  684. '/mnt/subvol2/.borgmatic-1234',
  685. ).once()
  686. flexmock(module.shutil).should_receive('rmtree').with_args(
  687. '/mnt/subvol2/.borgmatic-5678',
  688. ).never()
  689. module.remove_data_source_dumps(
  690. hook_config=config['btrfs'],
  691. config=config,
  692. borgmatic_runtime_directory='/run/borgmatic',
  693. dry_run=False,
  694. )
  695. def test_remove_data_source_dumps_without_hook_configuration_bails():
  696. flexmock(module).should_receive('get_subvolumes').never()
  697. flexmock(module).should_receive('make_snapshot_path').never()
  698. flexmock(module.borgmatic.config.paths).should_receive(
  699. 'replace_temporary_subdirectory_with_glob',
  700. ).never()
  701. flexmock(module).should_receive('delete_snapshot').never()
  702. flexmock(module.shutil).should_receive('rmtree').never()
  703. module.remove_data_source_dumps(
  704. hook_config=None,
  705. config={'source_directories': '/mnt/subvolume'},
  706. borgmatic_runtime_directory='/run/borgmatic',
  707. dry_run=False,
  708. )
  709. def test_remove_data_source_dumps_with_get_subvolumes_file_not_found_error_bails():
  710. config = {'btrfs': {}}
  711. flexmock(module).should_receive('get_subvolumes').and_raise(FileNotFoundError)
  712. flexmock(module).should_receive('make_snapshot_path').never()
  713. flexmock(module.borgmatic.config.paths).should_receive(
  714. 'replace_temporary_subdirectory_with_glob',
  715. ).never()
  716. flexmock(module).should_receive('delete_snapshot').never()
  717. flexmock(module.shutil).should_receive('rmtree').never()
  718. module.remove_data_source_dumps(
  719. hook_config=config['btrfs'],
  720. config=config,
  721. borgmatic_runtime_directory='/run/borgmatic',
  722. dry_run=False,
  723. )
  724. def test_remove_data_source_dumps_with_get_subvolumes_called_process_error_bails():
  725. config = {'btrfs': {}}
  726. flexmock(module).should_receive('get_subvolumes').and_raise(
  727. module.subprocess.CalledProcessError(1, 'command', 'error'),
  728. )
  729. flexmock(module).should_receive('make_snapshot_path').never()
  730. flexmock(module.borgmatic.config.paths).should_receive(
  731. 'replace_temporary_subdirectory_with_glob',
  732. ).never()
  733. flexmock(module).should_receive('delete_snapshot').never()
  734. flexmock(module.shutil).should_receive('rmtree').never()
  735. module.remove_data_source_dumps(
  736. hook_config=config['btrfs'],
  737. config=config,
  738. borgmatic_runtime_directory='/run/borgmatic',
  739. dry_run=False,
  740. )
  741. def test_remove_data_source_dumps_with_dry_run_skips_deletes():
  742. config = {'btrfs': {}}
  743. flexmock(module).should_receive('get_subvolumes').and_return(
  744. (
  745. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  746. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  747. ),
  748. )
  749. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  750. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1',
  751. )
  752. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  753. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2',
  754. )
  755. flexmock(module.borgmatic.config.paths).should_receive(
  756. 'replace_temporary_subdirectory_with_glob',
  757. ).with_args(
  758. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  759. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  760. ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
  761. flexmock(module.borgmatic.config.paths).should_receive(
  762. 'replace_temporary_subdirectory_with_glob',
  763. ).with_args(
  764. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  765. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  766. ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
  767. flexmock(module.glob).should_receive('glob').with_args(
  768. '/mnt/subvol1/.borgmatic-*/mnt/subvol1',
  769. ).and_return(
  770. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'),
  771. )
  772. flexmock(module.glob).should_receive('glob').with_args(
  773. '/mnt/subvol2/.borgmatic-*/mnt/subvol2',
  774. ).and_return(
  775. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'),
  776. )
  777. flexmock(module.os.path).should_receive('isdir').with_args(
  778. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  779. ).and_return(True)
  780. flexmock(module.os.path).should_receive('isdir').with_args(
  781. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1',
  782. ).and_return(True)
  783. flexmock(module.os.path).should_receive('isdir').with_args(
  784. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  785. ).and_return(True)
  786. flexmock(module.os.path).should_receive('isdir').with_args(
  787. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2',
  788. ).and_return(False)
  789. flexmock(module).should_receive('delete_snapshot').never()
  790. flexmock(module.shutil).should_receive('rmtree').never()
  791. module.remove_data_source_dumps(
  792. hook_config=config['btrfs'],
  793. config=config,
  794. borgmatic_runtime_directory='/run/borgmatic',
  795. dry_run=True,
  796. )
  797. def test_remove_data_source_dumps_without_subvolumes_skips_deletes():
  798. config = {'btrfs': {}}
  799. flexmock(module).should_receive('get_subvolumes').and_return(())
  800. flexmock(module).should_receive('make_snapshot_path').never()
  801. flexmock(module.borgmatic.config.paths).should_receive(
  802. 'replace_temporary_subdirectory_with_glob',
  803. ).never()
  804. flexmock(module).should_receive('delete_snapshot').never()
  805. flexmock(module.shutil).should_receive('rmtree').never()
  806. module.remove_data_source_dumps(
  807. hook_config=config['btrfs'],
  808. config=config,
  809. borgmatic_runtime_directory='/run/borgmatic',
  810. dry_run=False,
  811. )
  812. def test_remove_data_source_without_snapshots_skips_deletes():
  813. config = {'btrfs': {}}
  814. flexmock(module).should_receive('get_subvolumes').and_return(
  815. (
  816. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  817. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  818. ),
  819. )
  820. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  821. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1',
  822. )
  823. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  824. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2',
  825. )
  826. flexmock(module.borgmatic.config.paths).should_receive(
  827. 'replace_temporary_subdirectory_with_glob',
  828. ).with_args(
  829. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  830. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  831. ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
  832. flexmock(module.borgmatic.config.paths).should_receive(
  833. 'replace_temporary_subdirectory_with_glob',
  834. ).with_args(
  835. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  836. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  837. ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
  838. flexmock(module.glob).should_receive('glob').and_return(())
  839. flexmock(module.os.path).should_receive('isdir').never()
  840. flexmock(module).should_receive('delete_snapshot').never()
  841. flexmock(module.shutil).should_receive('rmtree').never()
  842. module.remove_data_source_dumps(
  843. hook_config=config['btrfs'],
  844. config=config,
  845. borgmatic_runtime_directory='/run/borgmatic',
  846. dry_run=False,
  847. )
  848. def test_remove_data_source_dumps_with_delete_snapshot_file_not_found_error_bails():
  849. config = {'btrfs': {}}
  850. flexmock(module).should_receive('get_subvolumes').and_return(
  851. (
  852. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  853. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  854. ),
  855. )
  856. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  857. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1',
  858. )
  859. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  860. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2',
  861. )
  862. flexmock(module.borgmatic.config.paths).should_receive(
  863. 'replace_temporary_subdirectory_with_glob',
  864. ).with_args(
  865. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  866. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  867. ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
  868. flexmock(module.borgmatic.config.paths).should_receive(
  869. 'replace_temporary_subdirectory_with_glob',
  870. ).with_args(
  871. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  872. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  873. ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
  874. flexmock(module.glob).should_receive('glob').with_args(
  875. '/mnt/subvol1/.borgmatic-*/mnt/subvol1',
  876. ).and_return(
  877. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'),
  878. )
  879. flexmock(module.glob).should_receive('glob').with_args(
  880. '/mnt/subvol2/.borgmatic-*/mnt/subvol2',
  881. ).and_return(
  882. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'),
  883. )
  884. flexmock(module.os.path).should_receive('isdir').with_args(
  885. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  886. ).and_return(True)
  887. flexmock(module.os.path).should_receive('isdir').with_args(
  888. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1',
  889. ).and_return(True)
  890. flexmock(module.os.path).should_receive('isdir').with_args(
  891. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  892. ).and_return(True)
  893. flexmock(module.os.path).should_receive('isdir').with_args(
  894. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2',
  895. ).and_return(False)
  896. flexmock(module).should_receive('delete_snapshot').and_raise(FileNotFoundError)
  897. flexmock(module.shutil).should_receive('rmtree').never()
  898. module.remove_data_source_dumps(
  899. hook_config=config['btrfs'],
  900. config=config,
  901. borgmatic_runtime_directory='/run/borgmatic',
  902. dry_run=False,
  903. )
  904. def test_remove_data_source_dumps_with_delete_snapshot_called_process_error_bails():
  905. config = {'btrfs': {}}
  906. flexmock(module).should_receive('get_subvolumes').and_return(
  907. (
  908. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  909. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  910. ),
  911. )
  912. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  913. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1',
  914. )
  915. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  916. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2',
  917. )
  918. flexmock(module.borgmatic.config.paths).should_receive(
  919. 'replace_temporary_subdirectory_with_glob',
  920. ).with_args(
  921. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  922. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  923. ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
  924. flexmock(module.borgmatic.config.paths).should_receive(
  925. 'replace_temporary_subdirectory_with_glob',
  926. ).with_args(
  927. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  928. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  929. ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
  930. flexmock(module.glob).should_receive('glob').with_args(
  931. '/mnt/subvol1/.borgmatic-*/mnt/subvol1',
  932. ).and_return(
  933. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'),
  934. )
  935. flexmock(module.glob).should_receive('glob').with_args(
  936. '/mnt/subvol2/.borgmatic-*/mnt/subvol2',
  937. ).and_return(
  938. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'),
  939. )
  940. flexmock(module.os.path).should_receive('isdir').with_args(
  941. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  942. ).and_return(True)
  943. flexmock(module.os.path).should_receive('isdir').with_args(
  944. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1',
  945. ).and_return(True)
  946. flexmock(module.os.path).should_receive('isdir').with_args(
  947. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  948. ).and_return(True)
  949. flexmock(module.os.path).should_receive('isdir').with_args(
  950. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2',
  951. ).and_return(False)
  952. flexmock(module).should_receive('delete_snapshot').and_raise(
  953. module.subprocess.CalledProcessError(1, 'command', 'error'),
  954. )
  955. flexmock(module.shutil).should_receive('rmtree').never()
  956. module.remove_data_source_dumps(
  957. hook_config=config['btrfs'],
  958. config=config,
  959. borgmatic_runtime_directory='/run/borgmatic',
  960. dry_run=False,
  961. )
  962. def test_remove_data_source_dumps_with_root_subvolume_skips_duplicate_removal():
  963. config = {'btrfs': {}}
  964. flexmock(module).should_receive('get_subvolumes').and_return(
  965. (module.Subvolume('/', contained_patterns=(Pattern('/etc'),)),),
  966. )
  967. flexmock(module).should_receive('make_snapshot_path').with_args('/').and_return(
  968. '/.borgmatic-1234',
  969. )
  970. flexmock(module.borgmatic.config.paths).should_receive(
  971. 'replace_temporary_subdirectory_with_glob',
  972. ).with_args(
  973. '/.borgmatic-1234',
  974. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  975. ).and_return('/.borgmatic-*')
  976. flexmock(module.glob).should_receive('glob').with_args('/.borgmatic-*').and_return(
  977. ('/.borgmatic-1234', '/.borgmatic-5678'),
  978. )
  979. flexmock(module.os.path).should_receive('isdir').with_args('/.borgmatic-1234').and_return(
  980. True,
  981. ).and_return(False)
  982. flexmock(module.os.path).should_receive('isdir').with_args('/.borgmatic-5678').and_return(
  983. True,
  984. ).and_return(False)
  985. flexmock(module).should_receive('delete_snapshot').with_args('btrfs', '/.borgmatic-1234').once()
  986. flexmock(module).should_receive('delete_snapshot').with_args('btrfs', '/.borgmatic-5678').once()
  987. flexmock(module.os.path).should_receive('isdir').with_args('').and_return(False)
  988. flexmock(module.shutil).should_receive('rmtree').never()
  989. module.remove_data_source_dumps(
  990. hook_config=config['btrfs'],
  991. config=config,
  992. borgmatic_runtime_directory='/run/borgmatic',
  993. dry_run=False,
  994. )