2
0

test_zfs.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  1. import os
  2. import pytest
  3. from flexmock import flexmock
  4. from borgmatic.borg.pattern import Pattern, Pattern_source, Pattern_style, Pattern_type
  5. from borgmatic.hooks.data_source import zfs as module
  6. def test_get_datasets_to_backup_filters_datasets_by_patterns():
  7. flexmock(module.borgmatic.execute).should_receive(
  8. 'execute_command_and_capture_output',
  9. ).and_return(
  10. 'dataset\t/dataset\ton\t-\nother\t/other\ton\t-',
  11. )
  12. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  13. 'get_contained_patterns',
  14. ).with_args('/dataset', object).and_return(
  15. (
  16. Pattern(
  17. '/dataset',
  18. Pattern_type.ROOT,
  19. source=Pattern_source.CONFIG,
  20. ),
  21. ),
  22. )
  23. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  24. 'get_contained_patterns',
  25. ).with_args('/other', object).and_return(())
  26. assert module.get_datasets_to_backup(
  27. 'zfs',
  28. patterns=(
  29. Pattern(
  30. '/foo',
  31. Pattern_type.ROOT,
  32. source=Pattern_source.CONFIG,
  33. ),
  34. Pattern(
  35. '/dataset',
  36. Pattern_type.ROOT,
  37. source=Pattern_source.CONFIG,
  38. ),
  39. Pattern(
  40. '/bar',
  41. Pattern_type.ROOT,
  42. source=Pattern_source.CONFIG,
  43. ),
  44. ),
  45. ) == (
  46. module.Dataset(
  47. name='dataset',
  48. mount_point='/dataset',
  49. contained_patterns=(
  50. Pattern(
  51. '/dataset',
  52. Pattern_type.ROOT,
  53. source=Pattern_source.CONFIG,
  54. ),
  55. ),
  56. ),
  57. )
  58. def test_get_datasets_to_backup_skips_non_root_patterns():
  59. flexmock(module.borgmatic.execute).should_receive(
  60. 'execute_command_and_capture_output',
  61. ).and_return(
  62. 'dataset\t/dataset\ton\t-\nother\t/other\ton\t-',
  63. )
  64. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  65. 'get_contained_patterns',
  66. ).with_args('/dataset', object).and_return(
  67. (
  68. Pattern(
  69. '/dataset',
  70. Pattern_type.EXCLUDE,
  71. source=Pattern_source.CONFIG,
  72. ),
  73. ),
  74. )
  75. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  76. 'get_contained_patterns',
  77. ).with_args('/other', object).and_return(())
  78. assert (
  79. module.get_datasets_to_backup(
  80. 'zfs',
  81. patterns=(
  82. Pattern(
  83. '/foo',
  84. Pattern_type.ROOT,
  85. source=Pattern_source.CONFIG,
  86. ),
  87. Pattern(
  88. '/dataset',
  89. Pattern_type.EXCLUDE,
  90. source=Pattern_source.CONFIG,
  91. ),
  92. Pattern(
  93. '/bar',
  94. Pattern_type.ROOT,
  95. source=Pattern_source.CONFIG,
  96. ),
  97. ),
  98. )
  99. == ()
  100. )
  101. def test_get_datasets_to_backup_skips_non_config_patterns():
  102. flexmock(module.borgmatic.execute).should_receive(
  103. 'execute_command_and_capture_output',
  104. ).and_return(
  105. 'dataset\t/dataset\ton\t-\nother\t/other\ton\t-',
  106. )
  107. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  108. 'get_contained_patterns',
  109. ).with_args('/dataset', object).and_return(
  110. (
  111. Pattern(
  112. '/dataset',
  113. Pattern_type.ROOT,
  114. source=Pattern_source.HOOK,
  115. ),
  116. ),
  117. )
  118. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  119. 'get_contained_patterns',
  120. ).with_args('/other', object).and_return(())
  121. assert (
  122. module.get_datasets_to_backup(
  123. 'zfs',
  124. patterns=(
  125. Pattern(
  126. '/foo',
  127. Pattern_type.ROOT,
  128. source=Pattern_source.CONFIG,
  129. ),
  130. Pattern(
  131. '/dataset',
  132. Pattern_type.ROOT,
  133. source=Pattern_source.HOOK,
  134. ),
  135. Pattern(
  136. '/bar',
  137. Pattern_type.ROOT,
  138. source=Pattern_source.CONFIG,
  139. ),
  140. ),
  141. )
  142. == ()
  143. )
  144. def test_get_datasets_to_backup_filters_datasets_by_user_property():
  145. flexmock(module.borgmatic.execute).should_receive(
  146. 'execute_command_and_capture_output',
  147. ).and_return(
  148. 'dataset\t/dataset\ton\tauto\nother\t/other\ton\t-',
  149. )
  150. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  151. 'get_contained_patterns',
  152. ).with_args('/dataset', object).and_return(())
  153. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  154. 'get_contained_patterns',
  155. ).with_args('/other', object).and_return(())
  156. assert module.get_datasets_to_backup(
  157. 'zfs',
  158. patterns=(Pattern('/foo'), Pattern('/bar')),
  159. ) == (
  160. module.Dataset(
  161. name='dataset',
  162. mount_point='/dataset',
  163. auto_backup=True,
  164. contained_patterns=(Pattern('/dataset', source=Pattern_source.HOOK),),
  165. ),
  166. )
  167. def test_get_datasets_to_backup_filters_datasets_by_canmount_property():
  168. flexmock(module.borgmatic.execute).should_receive(
  169. 'execute_command_and_capture_output',
  170. ).and_return(
  171. 'dataset\t/dataset\toff\t-\nother\t/other\ton\t-',
  172. )
  173. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  174. 'get_contained_patterns',
  175. ).with_args('/dataset', object).and_return((Pattern('/dataset'),))
  176. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  177. 'get_contained_patterns',
  178. ).with_args('/other', object).and_return(())
  179. assert (
  180. module.get_datasets_to_backup(
  181. 'zfs',
  182. patterns=(
  183. Pattern('/foo'),
  184. Pattern('/dataset'),
  185. Pattern('/bar'),
  186. ),
  187. )
  188. == ()
  189. )
  190. def test_get_datasets_to_backup_with_invalid_list_output_raises():
  191. flexmock(module.borgmatic.execute).should_receive(
  192. 'execute_command_and_capture_output',
  193. ).and_return(
  194. 'dataset',
  195. )
  196. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  197. 'get_contained_patterns',
  198. ).never()
  199. with pytest.raises(ValueError, match='zfs'):
  200. module.get_datasets_to_backup('zfs', patterns=(Pattern('/foo'), Pattern('/bar')))
  201. def test_get_all_dataset_mount_points_omits_none():
  202. flexmock(module.borgmatic.execute).should_receive(
  203. 'execute_command_and_capture_output',
  204. ).and_return(
  205. '/dataset\nnone\n/other',
  206. )
  207. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  208. 'get_contained_patterns',
  209. ).and_return((Pattern('/dataset'),))
  210. assert module.get_all_dataset_mount_points('zfs') == (
  211. ('/dataset'),
  212. ('/other'),
  213. )
  214. def test_get_all_dataset_mount_points_omits_duplicates():
  215. flexmock(module.borgmatic.execute).should_receive(
  216. 'execute_command_and_capture_output',
  217. ).and_return(
  218. '/dataset\n/other\n/dataset\n/other',
  219. )
  220. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  221. 'get_contained_patterns',
  222. ).and_return((Pattern('/dataset'),))
  223. assert module.get_all_dataset_mount_points('zfs') == (
  224. ('/dataset'),
  225. ('/other'),
  226. )
  227. @pytest.mark.parametrize(
  228. 'pattern,expected_pattern',
  229. (
  230. (
  231. Pattern('/foo/bar/baz'),
  232. Pattern('/run/borgmatic/zfs_snapshots/b33f/./foo/bar/baz'),
  233. ),
  234. (Pattern('/foo/bar'), Pattern('/run/borgmatic/zfs_snapshots/b33f/./foo/bar')),
  235. (
  236. Pattern('^/foo/bar', Pattern_type.INCLUDE, Pattern_style.REGULAR_EXPRESSION),
  237. Pattern(
  238. '^/run/borgmatic/zfs_snapshots/b33f/./foo/bar',
  239. Pattern_type.INCLUDE,
  240. Pattern_style.REGULAR_EXPRESSION,
  241. ),
  242. ),
  243. (
  244. Pattern('/foo/bar', Pattern_type.INCLUDE, Pattern_style.REGULAR_EXPRESSION),
  245. Pattern(
  246. '/run/borgmatic/zfs_snapshots/b33f/./foo/bar',
  247. Pattern_type.INCLUDE,
  248. Pattern_style.REGULAR_EXPRESSION,
  249. ),
  250. ),
  251. (Pattern('/foo'), Pattern('/run/borgmatic/zfs_snapshots/b33f/./foo')),
  252. (Pattern('/'), Pattern('/run/borgmatic/zfs_snapshots/b33f/./')),
  253. (
  254. Pattern('/foo/./bar/baz'),
  255. Pattern('/run/borgmatic/zfs_snapshots/b33f/foo/./bar/baz'),
  256. ),
  257. ),
  258. )
  259. def test_make_borg_snapshot_pattern_includes_slashdot_hack_and_stripped_pattern_path(
  260. pattern,
  261. expected_pattern,
  262. ):
  263. flexmock(module.hashlib).should_receive('shake_256').and_return(
  264. flexmock(hexdigest=lambda length: 'b33f'),
  265. )
  266. assert (
  267. module.make_borg_snapshot_pattern(
  268. pattern,
  269. flexmock(mount_point='/something'),
  270. '/run/borgmatic',
  271. )
  272. == expected_pattern
  273. )
  274. def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
  275. dataset = flexmock(
  276. name='dataset',
  277. mount_point='/mnt/dataset',
  278. contained_patterns=(Pattern('/mnt/dataset/subdir'),),
  279. )
  280. flexmock(module).should_receive('get_datasets_to_backup').and_return((dataset,))
  281. flexmock(module.os).should_receive('getpid').and_return(1234)
  282. full_snapshot_name = 'dataset@borgmatic-1234'
  283. flexmock(module).should_receive('snapshot_dataset').with_args(
  284. 'zfs',
  285. full_snapshot_name,
  286. ).once()
  287. flexmock(module.hashlib).should_receive('shake_256').and_return(
  288. flexmock(hexdigest=lambda length: 'b33f'),
  289. )
  290. snapshot_mount_path = '/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset'
  291. flexmock(module).should_receive('mount_snapshot').with_args(
  292. 'mount',
  293. full_snapshot_name,
  294. module.os.path.normpath(snapshot_mount_path),
  295. ).once()
  296. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  297. Pattern('/mnt/dataset/subdir'),
  298. dataset,
  299. '/run/borgmatic',
  300. ).and_return(Pattern('/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir'))
  301. patterns = [Pattern('/mnt/dataset/subdir')]
  302. assert (
  303. module.dump_data_sources(
  304. hook_config={},
  305. config={'source_directories': '/mnt/dataset', 'zfs': {}},
  306. config_paths=('test.yaml',),
  307. borgmatic_runtime_directory='/run/borgmatic',
  308. patterns=patterns,
  309. dry_run=False,
  310. )
  311. == []
  312. )
  313. assert patterns == [Pattern(os.path.join(snapshot_mount_path, 'subdir'))]
  314. def test_dump_data_sources_with_no_datasets_skips_snapshots():
  315. flexmock(module).should_receive('get_datasets_to_backup').and_return(())
  316. flexmock(module.os).should_receive('getpid').and_return(1234)
  317. flexmock(module).should_receive('snapshot_dataset').never()
  318. flexmock(module).should_receive('mount_snapshot').never()
  319. patterns = [Pattern('/mnt/dataset')]
  320. assert (
  321. module.dump_data_sources(
  322. hook_config={},
  323. config={'patterns': flexmock(), 'zfs': {}},
  324. config_paths=('test.yaml',),
  325. borgmatic_runtime_directory='/run/borgmatic',
  326. patterns=patterns,
  327. dry_run=False,
  328. )
  329. == []
  330. )
  331. assert patterns == [Pattern('/mnt/dataset')]
  332. def test_dump_data_sources_uses_custom_commands():
  333. dataset = flexmock(
  334. name='dataset',
  335. mount_point='/mnt/dataset',
  336. contained_patterns=(Pattern('/mnt/dataset/subdir'),),
  337. )
  338. flexmock(module).should_receive('get_datasets_to_backup').and_return((dataset,))
  339. flexmock(module.os).should_receive('getpid').and_return(1234)
  340. full_snapshot_name = 'dataset@borgmatic-1234'
  341. flexmock(module).should_receive('snapshot_dataset').with_args(
  342. '/usr/local/bin/zfs',
  343. full_snapshot_name,
  344. ).once()
  345. flexmock(module.hashlib).should_receive('shake_256').and_return(
  346. flexmock(hexdigest=lambda length: 'b33f'),
  347. )
  348. snapshot_mount_path = '/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset'
  349. flexmock(module).should_receive('mount_snapshot').with_args(
  350. '/usr/local/bin/mount',
  351. full_snapshot_name,
  352. module.os.path.normpath(snapshot_mount_path),
  353. ).once()
  354. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  355. Pattern('/mnt/dataset/subdir'),
  356. dataset,
  357. '/run/borgmatic',
  358. ).and_return(Pattern('/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir'))
  359. patterns = [Pattern('/mnt/dataset/subdir')]
  360. hook_config = {
  361. 'zfs_command': '/usr/local/bin/zfs',
  362. 'mount_command': '/usr/local/bin/mount',
  363. }
  364. assert (
  365. module.dump_data_sources(
  366. hook_config=hook_config,
  367. config={
  368. 'patterns': flexmock(),
  369. 'zfs': hook_config,
  370. },
  371. config_paths=('test.yaml',),
  372. borgmatic_runtime_directory='/run/borgmatic',
  373. patterns=patterns,
  374. dry_run=False,
  375. )
  376. == []
  377. )
  378. assert patterns == [Pattern(os.path.join(snapshot_mount_path, 'subdir'))]
  379. def test_dump_data_sources_with_dry_run_skips_commands_and_does_not_touch_patterns():
  380. flexmock(module).should_receive('get_datasets_to_backup').and_return(
  381. (flexmock(name='dataset', mount_point='/mnt/dataset'),),
  382. )
  383. flexmock(module.os).should_receive('getpid').and_return(1234)
  384. flexmock(module).should_receive('snapshot_dataset').never()
  385. flexmock(module).should_receive('mount_snapshot').never()
  386. patterns = [Pattern('/mnt/dataset')]
  387. assert (
  388. module.dump_data_sources(
  389. hook_config={},
  390. config={'patterns': ('R /mnt/dataset',), 'zfs': {}},
  391. config_paths=('test.yaml',),
  392. borgmatic_runtime_directory='/run/borgmatic',
  393. patterns=patterns,
  394. dry_run=True,
  395. )
  396. == []
  397. )
  398. assert patterns == [Pattern('/mnt/dataset')]
  399. def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained_patterns():
  400. dataset = flexmock(
  401. name='dataset',
  402. mount_point='/mnt/dataset',
  403. contained_patterns=(Pattern('/mnt/dataset/subdir'),),
  404. )
  405. flexmock(module).should_receive('get_datasets_to_backup').and_return((dataset,))
  406. flexmock(module.os).should_receive('getpid').and_return(1234)
  407. full_snapshot_name = 'dataset@borgmatic-1234'
  408. flexmock(module).should_receive('snapshot_dataset').with_args(
  409. 'zfs',
  410. full_snapshot_name,
  411. ).once()
  412. flexmock(module.hashlib).should_receive('shake_256').and_return(
  413. flexmock(hexdigest=lambda length: 'b33f'),
  414. )
  415. snapshot_mount_path = '/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset'
  416. flexmock(module).should_receive('mount_snapshot').with_args(
  417. 'mount',
  418. full_snapshot_name,
  419. module.os.path.normpath(snapshot_mount_path),
  420. ).once()
  421. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  422. Pattern('/mnt/dataset/subdir'),
  423. dataset,
  424. '/run/borgmatic',
  425. ).and_return(Pattern('/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir'))
  426. patterns = [Pattern('/hmm')]
  427. assert (
  428. module.dump_data_sources(
  429. hook_config={},
  430. config={'patterns': ('R /mnt/dataset',), 'zfs': {}},
  431. config_paths=('test.yaml',),
  432. borgmatic_runtime_directory='/run/borgmatic',
  433. patterns=patterns,
  434. dry_run=False,
  435. )
  436. == []
  437. )
  438. assert patterns == [Pattern('/hmm'), Pattern(os.path.join(snapshot_mount_path, 'subdir'))]
  439. def test_get_all_snapshots_parses_list_output():
  440. flexmock(module.borgmatic.execute).should_receive(
  441. 'execute_command_and_capture_output',
  442. ).and_return(
  443. 'dataset1@borgmatic-1234\ndataset2@borgmatic-4567',
  444. )
  445. assert module.get_all_snapshots('zfs') == ('dataset1@borgmatic-1234', 'dataset2@borgmatic-4567')
  446. def test_remove_data_source_dumps_unmounts_and_destroys_snapshots():
  447. flexmock(module).should_receive('get_all_dataset_mount_points').and_return(('/mnt/dataset',))
  448. flexmock(module.borgmatic.config.paths).should_receive(
  449. 'replace_temporary_subdirectory_with_glob',
  450. ).and_return('/run/borgmatic')
  451. flexmock(module.glob).should_receive('glob').replace_with(
  452. lambda path: [path.replace('*', 'b33f')],
  453. )
  454. flexmock(module.os.path).should_receive('isdir').and_return(True)
  455. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  456. flexmock(module.shutil).should_receive('rmtree')
  457. flexmock(module).should_receive('unmount_snapshot').with_args(
  458. 'umount',
  459. '/run/borgmatic/zfs_snapshots/b33f/mnt/dataset',
  460. ).once()
  461. flexmock(module).should_receive('get_all_snapshots').and_return(
  462. ('dataset@borgmatic-1234', 'dataset@other', 'other@other', 'invalid'),
  463. )
  464. flexmock(module).should_receive('destroy_snapshot').with_args(
  465. 'zfs',
  466. 'dataset@borgmatic-1234',
  467. ).once()
  468. module.remove_data_source_dumps(
  469. hook_config={},
  470. config={'source_directories': '/mnt/dataset', 'zfs': {}},
  471. borgmatic_runtime_directory='/run/borgmatic',
  472. patterns=flexmock(),
  473. dry_run=False,
  474. )
  475. def test_remove_data_source_dumps_use_custom_commands():
  476. flexmock(module).should_receive('get_all_dataset_mount_points').and_return(('/mnt/dataset',))
  477. flexmock(module.borgmatic.config.paths).should_receive(
  478. 'replace_temporary_subdirectory_with_glob',
  479. ).and_return('/run/borgmatic')
  480. flexmock(module.glob).should_receive('glob').replace_with(
  481. lambda path: [path.replace('*', 'b33f')],
  482. )
  483. flexmock(module.os.path).should_receive('isdir').and_return(True)
  484. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  485. flexmock(module.shutil).should_receive('rmtree')
  486. flexmock(module).should_receive('unmount_snapshot').with_args(
  487. '/usr/local/bin/umount',
  488. '/run/borgmatic/zfs_snapshots/b33f/mnt/dataset',
  489. ).once()
  490. flexmock(module).should_receive('get_all_snapshots').and_return(
  491. ('dataset@borgmatic-1234', 'dataset@other', 'other@other', 'invalid'),
  492. )
  493. flexmock(module).should_receive('destroy_snapshot').with_args(
  494. '/usr/local/bin/zfs',
  495. 'dataset@borgmatic-1234',
  496. ).once()
  497. hook_config = {'zfs_command': '/usr/local/bin/zfs', 'umount_command': '/usr/local/bin/umount'}
  498. module.remove_data_source_dumps(
  499. hook_config=hook_config,
  500. config={'source_directories': '/mnt/dataset', 'zfs': hook_config},
  501. borgmatic_runtime_directory='/run/borgmatic',
  502. patterns=flexmock(),
  503. dry_run=False,
  504. )
  505. def test_remove_data_source_dumps_bails_for_missing_hook_configuration():
  506. flexmock(module).should_receive('get_all_dataset_mount_points').never()
  507. flexmock(module.borgmatic.config.paths).should_receive(
  508. 'replace_temporary_subdirectory_with_glob',
  509. ).never()
  510. module.remove_data_source_dumps(
  511. hook_config=None,
  512. config={'source_directories': '/mnt/dataset'},
  513. borgmatic_runtime_directory='/run/borgmatic',
  514. patterns=flexmock(),
  515. dry_run=False,
  516. )
  517. def test_remove_data_source_dumps_bails_for_missing_zfs_command():
  518. flexmock(module).should_receive('get_all_dataset_mount_points').and_raise(FileNotFoundError)
  519. flexmock(module.borgmatic.config.paths).should_receive(
  520. 'replace_temporary_subdirectory_with_glob',
  521. ).never()
  522. hook_config = {'zfs_command': 'wtf'}
  523. module.remove_data_source_dumps(
  524. hook_config=hook_config,
  525. config={'source_directories': '/mnt/dataset', 'zfs': hook_config},
  526. borgmatic_runtime_directory='/run/borgmatic',
  527. patterns=flexmock(),
  528. dry_run=False,
  529. )
  530. def test_remove_data_source_dumps_bails_for_zfs_command_error():
  531. flexmock(module).should_receive('get_all_dataset_mount_points').and_raise(
  532. module.subprocess.CalledProcessError(1, 'wtf'),
  533. )
  534. flexmock(module.borgmatic.config.paths).should_receive(
  535. 'replace_temporary_subdirectory_with_glob',
  536. ).never()
  537. hook_config = {'zfs_command': 'wtf'}
  538. module.remove_data_source_dumps(
  539. hook_config=hook_config,
  540. config={'source_directories': '/mnt/dataset', 'zfs': hook_config},
  541. borgmatic_runtime_directory='/run/borgmatic',
  542. patterns=flexmock(),
  543. dry_run=False,
  544. )
  545. def test_remove_data_source_dumps_bails_for_missing_umount_command():
  546. flexmock(module).should_receive('get_all_dataset_mount_points').and_return(('/mnt/dataset',))
  547. flexmock(module.borgmatic.config.paths).should_receive(
  548. 'replace_temporary_subdirectory_with_glob',
  549. ).and_return('/run/borgmatic')
  550. flexmock(module.glob).should_receive('glob').replace_with(
  551. lambda path: [path.replace('*', 'b33f')],
  552. )
  553. flexmock(module.os.path).should_receive('isdir').and_return(True)
  554. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  555. flexmock(module.shutil).should_receive('rmtree')
  556. flexmock(module).should_receive('unmount_snapshot').with_args(
  557. '/usr/local/bin/umount',
  558. '/run/borgmatic/zfs_snapshots/b33f/mnt/dataset',
  559. ).and_raise(FileNotFoundError)
  560. flexmock(module).should_receive('get_all_snapshots').never()
  561. flexmock(module).should_receive('destroy_snapshot').never()
  562. hook_config = {'zfs_command': '/usr/local/bin/zfs', 'umount_command': '/usr/local/bin/umount'}
  563. module.remove_data_source_dumps(
  564. hook_config=hook_config,
  565. config={'source_directories': '/mnt/dataset', 'zfs': hook_config},
  566. borgmatic_runtime_directory='/run/borgmatic',
  567. patterns=flexmock(),
  568. dry_run=False,
  569. )
  570. def test_remove_data_source_dumps_swallows_umount_command_error():
  571. flexmock(module).should_receive('get_all_dataset_mount_points').and_return(('/mnt/dataset',))
  572. flexmock(module.borgmatic.config.paths).should_receive(
  573. 'replace_temporary_subdirectory_with_glob',
  574. ).and_return('/run/borgmatic')
  575. flexmock(module.glob).should_receive('glob').replace_with(
  576. lambda path: [path.replace('*', 'b33f')],
  577. )
  578. flexmock(module.os.path).should_receive('isdir').and_return(True)
  579. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  580. flexmock(module.shutil).should_receive('rmtree')
  581. flexmock(module).should_receive('unmount_snapshot').with_args(
  582. '/usr/local/bin/umount',
  583. '/run/borgmatic/zfs_snapshots/b33f/mnt/dataset',
  584. ).and_raise(module.subprocess.CalledProcessError(1, 'wtf'))
  585. flexmock(module).should_receive('get_all_snapshots').and_return(
  586. ('dataset@borgmatic-1234', 'dataset@other', 'other@other', 'invalid'),
  587. )
  588. flexmock(module).should_receive('destroy_snapshot').with_args(
  589. '/usr/local/bin/zfs',
  590. 'dataset@borgmatic-1234',
  591. ).once()
  592. hook_config = {'zfs_command': '/usr/local/bin/zfs', 'umount_command': '/usr/local/bin/umount'}
  593. module.remove_data_source_dumps(
  594. hook_config=hook_config,
  595. config={'source_directories': '/mnt/dataset', 'zfs': hook_config},
  596. borgmatic_runtime_directory='/run/borgmatic',
  597. patterns=flexmock(),
  598. dry_run=False,
  599. )
  600. def test_remove_data_source_dumps_skips_unmount_snapshot_directories_that_are_not_actually_directories():
  601. flexmock(module).should_receive('get_all_dataset_mount_points').and_return(('/mnt/dataset',))
  602. flexmock(module.borgmatic.config.paths).should_receive(
  603. 'replace_temporary_subdirectory_with_glob',
  604. ).and_return('/run/borgmatic')
  605. flexmock(module.glob).should_receive('glob').replace_with(
  606. lambda path: [path.replace('*', 'b33f')],
  607. )
  608. flexmock(module.os.path).should_receive('isdir').and_return(False)
  609. flexmock(module.shutil).should_receive('rmtree').never()
  610. flexmock(module).should_receive('unmount_snapshot').never()
  611. flexmock(module).should_receive('get_all_snapshots').and_return(
  612. ('dataset@borgmatic-1234', 'dataset@other', 'other@other', 'invalid'),
  613. )
  614. flexmock(module).should_receive('destroy_snapshot').with_args(
  615. 'zfs',
  616. 'dataset@borgmatic-1234',
  617. ).once()
  618. module.remove_data_source_dumps(
  619. hook_config={},
  620. config={'source_directories': '/mnt/dataset', 'zfs': {}},
  621. borgmatic_runtime_directory='/run/borgmatic',
  622. patterns=flexmock(),
  623. dry_run=False,
  624. )
  625. def test_remove_data_source_dumps_skips_unmount_snapshot_mount_paths_that_are_not_actually_directories():
  626. flexmock(module).should_receive('get_all_dataset_mount_points').and_return(('/mnt/dataset',))
  627. flexmock(module.borgmatic.config.paths).should_receive(
  628. 'replace_temporary_subdirectory_with_glob',
  629. ).and_return('/run/borgmatic')
  630. flexmock(module.glob).should_receive('glob').replace_with(
  631. lambda path: [path.replace('*', 'b33f')],
  632. )
  633. flexmock(module.os.path).should_receive('isdir').with_args(
  634. '/run/borgmatic/zfs_snapshots/b33f',
  635. ).and_return(True)
  636. flexmock(module.os.path).should_receive('isdir').with_args(
  637. '/run/borgmatic/zfs_snapshots/b33f/mnt/dataset',
  638. ).and_return(False)
  639. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  640. flexmock(module.shutil).should_receive('rmtree')
  641. flexmock(module).should_receive('unmount_snapshot').never()
  642. flexmock(module).should_receive('get_all_snapshots').and_return(
  643. ('dataset@borgmatic-1234', 'dataset@other', 'other@other', 'invalid'),
  644. )
  645. flexmock(module).should_receive('destroy_snapshot').with_args(
  646. 'zfs',
  647. 'dataset@borgmatic-1234',
  648. ).once()
  649. module.remove_data_source_dumps(
  650. hook_config={},
  651. config={'source_directories': '/mnt/dataset', 'zfs': {}},
  652. borgmatic_runtime_directory='/run/borgmatic',
  653. patterns=flexmock(),
  654. dry_run=False,
  655. )
  656. def test_remove_data_source_dumps_skips_unmount_snapshot_mount_paths_that_are_empty():
  657. flexmock(module).should_receive('get_all_dataset_mount_points').and_return(('/mnt/dataset',))
  658. flexmock(module.borgmatic.config.paths).should_receive(
  659. 'replace_temporary_subdirectory_with_glob',
  660. ).and_return('/run/borgmatic')
  661. flexmock(module.glob).should_receive('glob').replace_with(
  662. lambda path: [path.replace('*', 'b33f')],
  663. )
  664. flexmock(module.os.path).should_receive('isdir').with_args(
  665. '/run/borgmatic/zfs_snapshots/b33f',
  666. ).and_return(True)
  667. flexmock(module.os.path).should_receive('isdir').with_args(
  668. '/run/borgmatic/zfs_snapshots/b33f/mnt/dataset',
  669. ).and_return(True)
  670. flexmock(module.os).should_receive('listdir').with_args(
  671. '/run/borgmatic/zfs_snapshots/b33f/mnt/dataset',
  672. ).and_return([])
  673. flexmock(module.shutil).should_receive('rmtree')
  674. flexmock(module).should_receive('unmount_snapshot').never()
  675. flexmock(module).should_receive('get_all_snapshots').and_return(
  676. ('dataset@borgmatic-1234', 'dataset@other', 'other@other', 'invalid'),
  677. )
  678. flexmock(module).should_receive('destroy_snapshot').with_args(
  679. 'zfs',
  680. 'dataset@borgmatic-1234',
  681. ).once()
  682. module.remove_data_source_dumps(
  683. hook_config={},
  684. config={'source_directories': '/mnt/dataset', 'zfs': {}},
  685. borgmatic_runtime_directory='/run/borgmatic',
  686. patterns=flexmock(),
  687. dry_run=False,
  688. )
  689. def test_remove_data_source_dumps_skips_unmount_snapshot_mount_paths_after_rmtree_succeeds():
  690. flexmock(module).should_receive('get_all_dataset_mount_points').and_return(('/mnt/dataset',))
  691. flexmock(module.borgmatic.config.paths).should_receive(
  692. 'replace_temporary_subdirectory_with_glob',
  693. ).and_return('/run/borgmatic')
  694. flexmock(module.glob).should_receive('glob').replace_with(
  695. lambda path: [path.replace('*', 'b33f')],
  696. )
  697. flexmock(module.os.path).should_receive('isdir').with_args(
  698. '/run/borgmatic/zfs_snapshots/b33f',
  699. ).and_return(True)
  700. flexmock(module.os.path).should_receive('isdir').with_args(
  701. '/run/borgmatic/zfs_snapshots/b33f/mnt/dataset',
  702. ).and_return(True).and_return(False)
  703. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  704. flexmock(module.shutil).should_receive('rmtree')
  705. flexmock(module).should_receive('unmount_snapshot').never()
  706. flexmock(module).should_receive('get_all_snapshots').and_return(
  707. ('dataset@borgmatic-1234', 'dataset@other', 'other@other', 'invalid'),
  708. )
  709. flexmock(module).should_receive('destroy_snapshot').with_args(
  710. 'zfs',
  711. 'dataset@borgmatic-1234',
  712. ).once()
  713. module.remove_data_source_dumps(
  714. hook_config={},
  715. config={'source_directories': '/mnt/dataset', 'zfs': {}},
  716. borgmatic_runtime_directory='/run/borgmatic',
  717. patterns=flexmock(),
  718. dry_run=False,
  719. )
  720. def test_remove_data_source_dumps_with_dry_run_skips_unmount_and_destroy():
  721. flexmock(module).should_receive('get_all_dataset_mount_points').and_return(('/mnt/dataset',))
  722. flexmock(module.borgmatic.config.paths).should_receive(
  723. 'replace_temporary_subdirectory_with_glob',
  724. ).and_return('/run/borgmatic')
  725. flexmock(module.glob).should_receive('glob').replace_with(
  726. lambda path: [path.replace('*', 'b33f')],
  727. )
  728. flexmock(module.os.path).should_receive('isdir').and_return(True)
  729. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  730. flexmock(module.shutil).should_receive('rmtree').never()
  731. flexmock(module).should_receive('unmount_snapshot').never()
  732. flexmock(module).should_receive('get_all_snapshots').and_return(
  733. ('dataset@borgmatic-1234', 'dataset@other', 'other@other', 'invalid'),
  734. )
  735. flexmock(module).should_receive('destroy_snapshot').never()
  736. module.remove_data_source_dumps(
  737. hook_config={},
  738. config={'source_directories': '/mnt/dataset', 'zfs': {}},
  739. borgmatic_runtime_directory='/run/borgmatic',
  740. patterns=flexmock(),
  741. dry_run=True,
  742. )