test_btrfs.py 33 KB

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