test_btrfs.py 26 KB

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