test_btrfs.py 45 KB

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