test_zfs.py 27 KB

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