test_btrfs.py 44 KB

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