test_btrfs.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. from flexmock import flexmock
  2. from borgmatic.hooks.data_source import btrfs as module
  3. def test_get_filesystem_mount_points_parses_findmnt_output():
  4. flexmock(module.borgmatic.execute).should_receive(
  5. 'execute_command_and_capture_output'
  6. ).and_return(
  7. '/mnt0 /dev/loop0 btrfs rw,relatime,ssd,space_cache=v2,subvolid=5,subvol=/\n'
  8. '/mnt1 /dev/loop1 btrfs rw,relatime,ssd,space_cache=v2,subvolid=5,subvol=/\n'
  9. )
  10. assert module.get_filesystem_mount_points('findmnt') == ('/mnt0', '/mnt1')
  11. def test_get_subvolumes_for_filesystem_parses_subvolume_list_output():
  12. flexmock(module.borgmatic.execute).should_receive(
  13. 'execute_command_and_capture_output'
  14. ).and_return(
  15. 'ID 270 gen 107 top level 5 path subvol1\nID 272 gen 74 top level 5 path subvol2\n'
  16. )
  17. assert module.get_subvolumes_for_filesystem('btrfs', '/mnt') == ('/mnt', '/mnt/subvol1', '/mnt/subvol2')
  18. def test_get_subvolumes_for_filesystem_skips_empty_subvolume_paths():
  19. flexmock(module.borgmatic.execute).should_receive(
  20. 'execute_command_and_capture_output'
  21. ).and_return('\n \nID 272 gen 74 top level 5 path subvol2\n')
  22. assert module.get_subvolumes_for_filesystem('btrfs', '/mnt') == ('/mnt', '/mnt/subvol2')
  23. def test_get_subvolumes_for_filesystem_skips_empty_filesystem_mount_points():
  24. flexmock(module.borgmatic.execute).should_receive(
  25. 'execute_command_and_capture_output'
  26. ).and_return(
  27. 'ID 270 gen 107 top level 5 path subvol1\nID 272 gen 74 top level 5 path subvol2\n'
  28. )
  29. assert module.get_subvolumes_for_filesystem('btrfs', ' ') == ()
  30. def test_get_subvolumes_collects_subvolumes_matching_source_directories_from_all_filesystems():
  31. flexmock(module).should_receive('get_filesystem_mount_points').and_return(('/mnt1', '/mnt2'))
  32. flexmock(module).should_receive('get_subvolumes_for_filesystem').with_args(
  33. 'btrfs', '/mnt1'
  34. ).and_return(('/one', '/two'))
  35. flexmock(module).should_receive('get_subvolumes_for_filesystem').with_args(
  36. 'btrfs', '/mnt2'
  37. ).and_return(('/three', '/four'))
  38. for path in ('/one', '/four'):
  39. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  40. 'get_contained_directories'
  41. ).with_args(path, object).and_return((path,))
  42. for path in ('/two', '/three'):
  43. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  44. 'get_contained_directories'
  45. ).with_args(path, object).and_return(())
  46. assert module.get_subvolumes(
  47. 'btrfs', 'findmnt', source_directories=['/one', '/four', '/five', '/six', '/mnt2', '/mnt3']
  48. ) == (
  49. module.Subvolume('/four', contained_source_directories=('/four',)),
  50. module.Subvolume('/one', contained_source_directories=('/one',)),
  51. )
  52. def test_get_subvolumes_without_source_directories_collects_all_subvolumes_from_all_filesystems():
  53. flexmock(module).should_receive('get_filesystem_mount_points').and_return(('/mnt1', '/mnt2'))
  54. flexmock(module).should_receive('get_subvolumes_for_filesystem').with_args(
  55. 'btrfs', '/mnt1'
  56. ).and_return(('/one', '/two'))
  57. flexmock(module).should_receive('get_subvolumes_for_filesystem').with_args(
  58. 'btrfs', '/mnt2'
  59. ).and_return(('/three', '/four'))
  60. for path in ('/one', '/two', '/three', '/four'):
  61. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  62. 'get_contained_directories'
  63. ).with_args(path, object).and_return((path,))
  64. assert module.get_subvolumes('btrfs', 'findmnt') == (
  65. module.Subvolume('/four', contained_source_directories=('/four',)),
  66. module.Subvolume('/one', contained_source_directories=('/one',)),
  67. module.Subvolume('/three', contained_source_directories=('/three',)),
  68. module.Subvolume('/two', contained_source_directories=('/two',)),
  69. )
  70. def test_dump_data_sources_snapshots_each_subvolume_and_updates_source_directories():
  71. source_directories = ['/foo', '/mnt/subvol1']
  72. config = {'btrfs': {}}
  73. flexmock(module).should_receive('get_subvolumes').and_return(
  74. (
  75. module.Subvolume('/mnt/subvol1', contained_source_directories=('/mnt/subvol1',)),
  76. module.Subvolume('/mnt/subvol2', contained_source_directories=('/mnt/subvol2',)),
  77. )
  78. )
  79. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  80. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  81. )
  82. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  83. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  84. )
  85. flexmock(module).should_receive('snapshot_subvolume').with_args(
  86. 'btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  87. ).once()
  88. flexmock(module).should_receive('snapshot_subvolume').with_args(
  89. 'btrfs', '/mnt/subvol2', '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  90. ).once()
  91. flexmock(module).should_receive('make_snapshot_exclude_path').with_args(
  92. '/mnt/subvol1'
  93. ).and_return('/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234')
  94. flexmock(module).should_receive('make_snapshot_exclude_path').with_args(
  95. '/mnt/subvol2'
  96. ).and_return('/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234')
  97. flexmock(module).should_receive('make_borg_source_directory_path').with_args(
  98. '/mnt/subvol1', object
  99. ).and_return('/mnt/subvol1/.borgmatic-1234/mnt/subvol1')
  100. flexmock(module).should_receive('make_borg_source_directory_path').with_args(
  101. '/mnt/subvol2', object
  102. ).and_return('/mnt/subvol2/.borgmatic-1234/mnt/subvol2')
  103. assert (
  104. module.dump_data_sources(
  105. hook_config=config['btrfs'],
  106. config=config,
  107. log_prefix='test',
  108. config_paths=('test.yaml',),
  109. borgmatic_runtime_directory='/run/borgmatic',
  110. source_directories=source_directories,
  111. dry_run=False,
  112. )
  113. == []
  114. )
  115. assert source_directories == [
  116. '/foo',
  117. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  118. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  119. ]
  120. assert config == {
  121. 'btrfs': {},
  122. 'exclude_patterns': [
  123. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  124. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  125. ],
  126. }
  127. def test_dump_data_sources_uses_custom_btrfs_command_in_commands():
  128. source_directories = ['/foo', '/mnt/subvol1']
  129. config = {'btrfs': {'btrfs_command': '/usr/local/bin/btrfs'}}
  130. flexmock(module).should_receive('get_subvolumes').and_return(
  131. (module.Subvolume('/mnt/subvol1', contained_source_directories=('/mnt/subvol1',)),)
  132. )
  133. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  134. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  135. )
  136. flexmock(module).should_receive('snapshot_subvolume').with_args(
  137. '/usr/local/bin/btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  138. ).once()
  139. flexmock(module).should_receive('make_snapshot_exclude_path').with_args(
  140. '/mnt/subvol1'
  141. ).and_return('/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234')
  142. flexmock(module).should_receive('make_borg_source_directory_path').with_args(
  143. '/mnt/subvol1', object
  144. ).and_return('/mnt/subvol1/.borgmatic-1234/mnt/subvol1')
  145. assert (
  146. module.dump_data_sources(
  147. hook_config=config['btrfs'],
  148. config=config,
  149. log_prefix='test',
  150. config_paths=('test.yaml',),
  151. borgmatic_runtime_directory='/run/borgmatic',
  152. source_directories=source_directories,
  153. dry_run=False,
  154. )
  155. == []
  156. )
  157. assert source_directories == [
  158. '/foo',
  159. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  160. ]
  161. assert config == {
  162. 'btrfs': {
  163. 'btrfs_command': '/usr/local/bin/btrfs',
  164. },
  165. 'exclude_patterns': [
  166. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  167. ],
  168. }
  169. def test_dump_data_sources_uses_custom_findmnt_command_in_commands():
  170. source_directories = ['/foo', '/mnt/subvol1']
  171. config = {'btrfs': {'findmnt_command': '/usr/local/bin/findmnt'}}
  172. flexmock(module).should_receive('get_subvolumes').with_args(
  173. 'btrfs', '/usr/local/bin/findmnt', source_directories
  174. ).and_return((module.Subvolume('/mnt/subvol1', contained_source_directories=('/mnt/subvol1',)),)).once()
  175. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  176. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  177. )
  178. flexmock(module).should_receive('snapshot_subvolume').with_args(
  179. 'btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  180. ).once()
  181. flexmock(module).should_receive('make_snapshot_exclude_path').with_args(
  182. '/mnt/subvol1'
  183. ).and_return('/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234')
  184. flexmock(module).should_receive('make_borg_source_directory_path').with_args(
  185. '/mnt/subvol1', object
  186. ).and_return('/mnt/subvol1/.borgmatic-1234/mnt/subvol1')
  187. assert (
  188. module.dump_data_sources(
  189. hook_config=config['btrfs'],
  190. config=config,
  191. log_prefix='test',
  192. config_paths=('test.yaml',),
  193. borgmatic_runtime_directory='/run/borgmatic',
  194. source_directories=source_directories,
  195. dry_run=False,
  196. )
  197. == []
  198. )
  199. assert source_directories == [
  200. '/foo',
  201. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  202. ]
  203. assert config == {
  204. 'btrfs': {
  205. 'findmnt_command': '/usr/local/bin/findmnt',
  206. },
  207. 'exclude_patterns': [
  208. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  209. ],
  210. }
  211. def test_dump_data_sources_with_dry_run_skips_snapshot_and_source_directories_update():
  212. source_directories = ['/foo', '/mnt/subvol1']
  213. config = {'btrfs': {}}
  214. flexmock(module).should_receive('get_subvolumes').and_return(
  215. (module.Subvolume('/mnt/subvol1', contained_source_directories=('/mnt/subvol1',)),)
  216. )
  217. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  218. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  219. )
  220. flexmock(module).should_receive('snapshot_subvolume').never()
  221. flexmock(module).should_receive('make_snapshot_exclude_path').never()
  222. assert (
  223. module.dump_data_sources(
  224. hook_config=config['btrfs'],
  225. config=config,
  226. log_prefix='test',
  227. config_paths=('test.yaml',),
  228. borgmatic_runtime_directory='/run/borgmatic',
  229. source_directories=source_directories,
  230. dry_run=True,
  231. )
  232. == []
  233. )
  234. assert source_directories == ['/foo', '/mnt/subvol1']
  235. assert config == {'btrfs': {}}
  236. def test_dump_data_sources_without_matching_subvolumes_skips_snapshot_and_source_directories_update():
  237. source_directories = ['/foo', '/mnt/subvol1']
  238. config = {'btrfs': {}}
  239. flexmock(module).should_receive('get_subvolumes').and_return(())
  240. flexmock(module).should_receive('make_snapshot_path').never()
  241. flexmock(module).should_receive('snapshot_subvolume').never()
  242. flexmock(module).should_receive('make_snapshot_exclude_path').never()
  243. assert (
  244. module.dump_data_sources(
  245. hook_config=config['btrfs'],
  246. config=config,
  247. log_prefix='test',
  248. config_paths=('test.yaml',),
  249. borgmatic_runtime_directory='/run/borgmatic',
  250. source_directories=source_directories,
  251. dry_run=False,
  252. )
  253. == []
  254. )
  255. assert source_directories == ['/foo', '/mnt/subvol1']
  256. assert config == {'btrfs': {}}
  257. def test_dump_data_sources_snapshots_adds_to_existing_exclude_patterns():
  258. source_directories = ['/foo', '/mnt/subvol1']
  259. config = {'btrfs': {}, 'exclude_patterns': ['/bar']}
  260. flexmock(module).should_receive('get_subvolumes').and_return(
  261. (module.Subvolume('/mnt/subvol1', contained_source_directories=('/mnt/subvol1',)), module.Subvolume('/mnt/subvol2', contained_source_directories=('/mnt/subvol2',)))
  262. )
  263. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  264. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  265. )
  266. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  267. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  268. )
  269. flexmock(module).should_receive('snapshot_subvolume').with_args(
  270. 'btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  271. ).once()
  272. flexmock(module).should_receive('snapshot_subvolume').with_args(
  273. 'btrfs', '/mnt/subvol2', '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  274. ).once()
  275. flexmock(module).should_receive('make_snapshot_exclude_path').with_args(
  276. '/mnt/subvol1'
  277. ).and_return('/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234')
  278. flexmock(module).should_receive('make_snapshot_exclude_path').with_args(
  279. '/mnt/subvol2'
  280. ).and_return('/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234')
  281. flexmock(module).should_receive('make_borg_source_directory_path').with_args(
  282. '/mnt/subvol1', object
  283. ).and_return('/mnt/subvol1/.borgmatic-1234/mnt/subvol1')
  284. flexmock(module).should_receive('make_borg_source_directory_path').with_args(
  285. '/mnt/subvol2', object
  286. ).and_return('/mnt/subvol2/.borgmatic-1234/mnt/subvol2')
  287. assert (
  288. module.dump_data_sources(
  289. hook_config=config['btrfs'],
  290. config=config,
  291. log_prefix='test',
  292. config_paths=('test.yaml',),
  293. borgmatic_runtime_directory='/run/borgmatic',
  294. source_directories=source_directories,
  295. dry_run=False,
  296. )
  297. == []
  298. )
  299. assert source_directories == [
  300. '/foo',
  301. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  302. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  303. ]
  304. assert config == {
  305. 'btrfs': {},
  306. 'exclude_patterns': [
  307. '/bar',
  308. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  309. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  310. ],
  311. }
  312. def test_remove_data_source_dumps_deletes_snapshots():
  313. config = {'btrfs': {}}
  314. flexmock(module).should_receive('get_subvolumes').and_return(
  315. (module.Subvolume('/mnt/subvol1', contained_source_directories=('/mnt/subvol1',)), module.Subvolume('/mnt/subvol2', contained_source_directories=('/mnt/subvol2',)))
  316. )
  317. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  318. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  319. )
  320. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  321. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  322. )
  323. flexmock(module.borgmatic.config.paths).should_receive(
  324. 'replace_temporary_subdirectory_with_glob'
  325. ).with_args(
  326. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  327. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  328. ).and_return(
  329. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  330. )
  331. flexmock(module.borgmatic.config.paths).should_receive(
  332. 'replace_temporary_subdirectory_with_glob'
  333. ).with_args(
  334. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  335. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  336. ).and_return(
  337. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  338. )
  339. flexmock(module.glob).should_receive('glob').with_args(
  340. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  341. ).and_return(
  342. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  343. )
  344. flexmock(module.glob).should_receive('glob').with_args(
  345. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  346. ).and_return(
  347. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  348. )
  349. flexmock(module.os.path).should_receive('isdir').with_args(
  350. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  351. ).and_return(True)
  352. flexmock(module.os.path).should_receive('isdir').with_args(
  353. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  354. ).and_return(True)
  355. flexmock(module.os.path).should_receive('isdir').with_args(
  356. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  357. ).and_return(True)
  358. flexmock(module.os.path).should_receive('isdir').with_args(
  359. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  360. ).and_return(False)
  361. flexmock(module).should_receive('delete_snapshot').with_args(
  362. 'btrfs', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  363. ).once()
  364. flexmock(module).should_receive('delete_snapshot').with_args(
  365. 'btrfs', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  366. ).once()
  367. flexmock(module).should_receive('delete_snapshot').with_args(
  368. 'btrfs', '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  369. ).once()
  370. flexmock(module).should_receive('delete_snapshot').with_args(
  371. 'btrfs', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  372. ).never()
  373. flexmock(module.shutil).should_receive('rmtree').with_args(
  374. '/mnt/subvol1/.borgmatic-1234'
  375. ).once()
  376. flexmock(module.shutil).should_receive('rmtree').with_args(
  377. '/mnt/subvol1/.borgmatic-5678'
  378. ).once()
  379. flexmock(module.shutil).should_receive('rmtree').with_args(
  380. '/mnt/subvol2/.borgmatic-1234'
  381. ).once()
  382. flexmock(module.shutil).should_receive('rmtree').with_args(
  383. '/mnt/subvol2/.borgmatic-5678'
  384. ).never()
  385. module.remove_data_source_dumps(
  386. hook_config=config['btrfs'],
  387. config=config,
  388. log_prefix='test',
  389. borgmatic_runtime_directory='/run/borgmatic',
  390. dry_run=False,
  391. )
  392. def test_remove_data_source_dumps_with_get_subvolumes_file_not_found_error_bails():
  393. config = {'btrfs': {}}
  394. flexmock(module).should_receive('get_subvolumes').and_raise(FileNotFoundError)
  395. flexmock(module).should_receive('make_snapshot_path').never()
  396. flexmock(module.borgmatic.config.paths).should_receive(
  397. 'replace_temporary_subdirectory_with_glob'
  398. ).never()
  399. flexmock(module).should_receive('delete_snapshot').never()
  400. flexmock(module.shutil).should_receive('rmtree').never()
  401. module.remove_data_source_dumps(
  402. hook_config=config['btrfs'],
  403. config=config,
  404. log_prefix='test',
  405. borgmatic_runtime_directory='/run/borgmatic',
  406. dry_run=False,
  407. )
  408. def test_remove_data_source_dumps_with_get_subvolumes_called_process_error_bails():
  409. config = {'btrfs': {}}
  410. flexmock(module).should_receive('get_subvolumes').and_raise(
  411. module.subprocess.CalledProcessError(1, 'command', 'error')
  412. )
  413. flexmock(module).should_receive('make_snapshot_path').never()
  414. flexmock(module.borgmatic.config.paths).should_receive(
  415. 'replace_temporary_subdirectory_with_glob'
  416. ).never()
  417. flexmock(module).should_receive('delete_snapshot').never()
  418. flexmock(module.shutil).should_receive('rmtree').never()
  419. module.remove_data_source_dumps(
  420. hook_config=config['btrfs'],
  421. config=config,
  422. log_prefix='test',
  423. borgmatic_runtime_directory='/run/borgmatic',
  424. dry_run=False,
  425. )
  426. def test_remove_data_source_dumps_with_dry_run_skips_deletes():
  427. config = {'btrfs': {}}
  428. flexmock(module).should_receive('get_subvolumes').and_return(
  429. (module.Subvolume('/mnt/subvol1', contained_source_directories=('/mnt/subvol1',)), module.Subvolume('/mnt/subvol2', contained_source_directories=('/mnt/subvol2',)))
  430. )
  431. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  432. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  433. )
  434. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  435. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  436. )
  437. flexmock(module.borgmatic.config.paths).should_receive(
  438. 'replace_temporary_subdirectory_with_glob'
  439. ).with_args(
  440. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  441. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  442. ).and_return(
  443. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  444. )
  445. flexmock(module.borgmatic.config.paths).should_receive(
  446. 'replace_temporary_subdirectory_with_glob'
  447. ).with_args(
  448. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  449. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  450. ).and_return(
  451. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  452. )
  453. flexmock(module.glob).should_receive('glob').with_args(
  454. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  455. ).and_return(
  456. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  457. )
  458. flexmock(module.glob).should_receive('glob').with_args(
  459. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  460. ).and_return(
  461. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  462. )
  463. flexmock(module.os.path).should_receive('isdir').with_args(
  464. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  465. ).and_return(True)
  466. flexmock(module.os.path).should_receive('isdir').with_args(
  467. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  468. ).and_return(True)
  469. flexmock(module.os.path).should_receive('isdir').with_args(
  470. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  471. ).and_return(True)
  472. flexmock(module.os.path).should_receive('isdir').with_args(
  473. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  474. ).and_return(False)
  475. flexmock(module).should_receive('delete_snapshot').never()
  476. flexmock(module.shutil).should_receive('rmtree').never()
  477. module.remove_data_source_dumps(
  478. hook_config=config['btrfs'],
  479. config=config,
  480. log_prefix='test',
  481. borgmatic_runtime_directory='/run/borgmatic',
  482. dry_run=True,
  483. )
  484. def test_remove_data_source_dumps_without_subvolumes_skips_deletes():
  485. config = {'btrfs': {}}
  486. flexmock(module).should_receive('get_subvolumes').and_return(())
  487. flexmock(module).should_receive('make_snapshot_path').never()
  488. flexmock(module.borgmatic.config.paths).should_receive(
  489. 'replace_temporary_subdirectory_with_glob'
  490. ).never()
  491. flexmock(module).should_receive('delete_snapshot').never()
  492. flexmock(module.shutil).should_receive('rmtree').never()
  493. module.remove_data_source_dumps(
  494. hook_config=config['btrfs'],
  495. config=config,
  496. log_prefix='test',
  497. borgmatic_runtime_directory='/run/borgmatic',
  498. dry_run=False,
  499. )
  500. def test_remove_data_source_without_snapshots_skips_deletes():
  501. config = {'btrfs': {}}
  502. flexmock(module).should_receive('get_subvolumes').and_return(
  503. (module.Subvolume('/mnt/subvol1', contained_source_directories=('/mnt/subvol1',)), module.Subvolume('/mnt/subvol2', contained_source_directories=('/mnt/subvol2',)))
  504. )
  505. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  506. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  507. )
  508. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  509. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  510. )
  511. flexmock(module.borgmatic.config.paths).should_receive(
  512. 'replace_temporary_subdirectory_with_glob'
  513. ).with_args(
  514. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  515. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  516. ).and_return(
  517. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  518. )
  519. flexmock(module.borgmatic.config.paths).should_receive(
  520. 'replace_temporary_subdirectory_with_glob'
  521. ).with_args(
  522. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  523. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  524. ).and_return(
  525. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  526. )
  527. flexmock(module.glob).should_receive('glob').and_return(())
  528. flexmock(module.os.path).should_receive('isdir').never()
  529. flexmock(module).should_receive('delete_snapshot').never()
  530. flexmock(module.shutil).should_receive('rmtree').never()
  531. module.remove_data_source_dumps(
  532. hook_config=config['btrfs'],
  533. config=config,
  534. log_prefix='test',
  535. borgmatic_runtime_directory='/run/borgmatic',
  536. dry_run=False,
  537. )
  538. def test_remove_data_source_dumps_with_delete_snapshot_file_not_found_error_bails():
  539. config = {'btrfs': {}}
  540. flexmock(module).should_receive('get_subvolumes').and_return(
  541. (module.Subvolume('/mnt/subvol1', contained_source_directories=('/mnt/subvol1',)), module.Subvolume('/mnt/subvol2', contained_source_directories=('/mnt/subvol2',)))
  542. )
  543. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  544. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  545. )
  546. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  547. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  548. )
  549. flexmock(module.borgmatic.config.paths).should_receive(
  550. 'replace_temporary_subdirectory_with_glob'
  551. ).with_args(
  552. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  553. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  554. ).and_return(
  555. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  556. )
  557. flexmock(module.borgmatic.config.paths).should_receive(
  558. 'replace_temporary_subdirectory_with_glob'
  559. ).with_args(
  560. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  561. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  562. ).and_return(
  563. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  564. )
  565. flexmock(module.glob).should_receive('glob').with_args(
  566. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  567. ).and_return(
  568. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  569. )
  570. flexmock(module.glob).should_receive('glob').with_args(
  571. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  572. ).and_return(
  573. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  574. )
  575. flexmock(module.os.path).should_receive('isdir').with_args(
  576. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  577. ).and_return(True)
  578. flexmock(module.os.path).should_receive('isdir').with_args(
  579. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  580. ).and_return(True)
  581. flexmock(module.os.path).should_receive('isdir').with_args(
  582. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  583. ).and_return(True)
  584. flexmock(module.os.path).should_receive('isdir').with_args(
  585. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  586. ).and_return(False)
  587. flexmock(module).should_receive('delete_snapshot').and_raise(FileNotFoundError)
  588. flexmock(module.shutil).should_receive('rmtree').never()
  589. module.remove_data_source_dumps(
  590. hook_config=config['btrfs'],
  591. config=config,
  592. log_prefix='test',
  593. borgmatic_runtime_directory='/run/borgmatic',
  594. dry_run=False,
  595. )
  596. def test_remove_data_source_dumps_with_delete_snapshot_called_process_error_bails():
  597. config = {'btrfs': {}}
  598. flexmock(module).should_receive('get_subvolumes').and_return(
  599. (
  600. module.Subvolume('/mnt/subvol1', contained_source_directories=('/mnt/subvol1',)),
  601. module.Subvolume('/mnt/subvol2', contained_source_directories=('/mnt/subvol2',)),
  602. )
  603. )
  604. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  605. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  606. )
  607. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  608. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  609. )
  610. flexmock(module.borgmatic.config.paths).should_receive(
  611. 'replace_temporary_subdirectory_with_glob'
  612. ).with_args(
  613. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  614. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  615. ).and_return(
  616. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  617. )
  618. flexmock(module.borgmatic.config.paths).should_receive(
  619. 'replace_temporary_subdirectory_with_glob'
  620. ).with_args(
  621. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  622. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  623. ).and_return(
  624. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  625. )
  626. flexmock(module.glob).should_receive('glob').with_args(
  627. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  628. ).and_return(
  629. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  630. )
  631. flexmock(module.glob).should_receive('glob').with_args(
  632. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  633. ).and_return(
  634. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  635. )
  636. flexmock(module.os.path).should_receive('isdir').with_args(
  637. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  638. ).and_return(True)
  639. flexmock(module.os.path).should_receive('isdir').with_args(
  640. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  641. ).and_return(True)
  642. flexmock(module.os.path).should_receive('isdir').with_args(
  643. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  644. ).and_return(True)
  645. flexmock(module.os.path).should_receive('isdir').with_args(
  646. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  647. ).and_return(False)
  648. flexmock(module).should_receive('delete_snapshot').and_raise(
  649. module.subprocess.CalledProcessError(1, 'command', 'error')
  650. )
  651. flexmock(module.shutil).should_receive('rmtree').never()
  652. module.remove_data_source_dumps(
  653. hook_config=config['btrfs'],
  654. config=config,
  655. log_prefix='test',
  656. borgmatic_runtime_directory='/run/borgmatic',
  657. dry_run=False,
  658. )