test_btrfs.py 29 KB

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