test_btrfs.py 40 KB

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