test_btrfs.py 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157
  1. import pytest
  2. from flexmock import flexmock
  3. from borgmatic.borg.pattern import Pattern, Pattern_source, Pattern_style, Pattern_type
  4. from borgmatic.hooks.data_source import btrfs as module
  5. def test_path_is_a_subvolume_with_btrfs_success_call_returns_true():
  6. module.path_is_a_subvolume.cache_clear()
  7. flexmock(module.borgmatic.execute).should_receive(
  8. 'execute_command',
  9. ).with_args(('btrfs', 'subvolume', 'show', '/mnt0'), output_log_level=None, close_fds=True)
  10. assert module.path_is_a_subvolume('btrfs', '/mnt0') is True
  11. def test_path_is_a_subvolume_with_btrfs_error_returns_false():
  12. module.path_is_a_subvolume.cache_clear()
  13. flexmock(module.borgmatic.execute).should_receive(
  14. 'execute_command',
  15. ).with_args(
  16. ('btrfs', 'subvolume', 'show', '/mnt0'), output_log_level=None, close_fds=True
  17. ).and_raise(
  18. module.subprocess.CalledProcessError(1, 'btrfs'),
  19. )
  20. assert module.path_is_a_subvolume('btrfs', '/mnt0') is False
  21. def test_path_is_a_subvolume_caches_result_after_first_call():
  22. module.path_is_a_subvolume.cache_clear()
  23. flexmock(module.borgmatic.execute).should_receive(
  24. 'execute_command',
  25. ).once()
  26. assert module.path_is_a_subvolume('btrfs', '/mnt0') is True
  27. assert module.path_is_a_subvolume('btrfs', '/mnt0') is True
  28. def test_get_subvolume_property_with_invalid_btrfs_output_errors():
  29. module.get_subvolume_property.cache_clear()
  30. flexmock(module.borgmatic.execute).should_receive(
  31. 'execute_command_and_capture_output',
  32. ).and_return('invalid')
  33. with pytest.raises(ValueError):
  34. module.get_subvolume_property('btrfs', '/foo', 'ro')
  35. def test_get_subvolume_property_with_true_output_returns_true_bool():
  36. module.get_subvolume_property.cache_clear()
  37. flexmock(module.borgmatic.execute).should_receive(
  38. 'execute_command_and_capture_output',
  39. ).and_return('ro=true')
  40. assert module.get_subvolume_property('btrfs', '/foo', 'ro') is True
  41. def test_get_subvolume_property_with_false_output_returns_false_bool():
  42. module.get_subvolume_property.cache_clear()
  43. flexmock(module.borgmatic.execute).should_receive(
  44. 'execute_command_and_capture_output',
  45. ).and_return('ro=false')
  46. assert module.get_subvolume_property('btrfs', '/foo', 'ro') is False
  47. def test_get_subvolume_property_passes_through_general_value():
  48. module.get_subvolume_property.cache_clear()
  49. flexmock(module.borgmatic.execute).should_receive(
  50. 'execute_command_and_capture_output',
  51. ).and_return('thing=value')
  52. assert module.get_subvolume_property('btrfs', '/foo', 'thing') == 'value'
  53. def test_get_subvolume_property_caches_result_after_first_call():
  54. module.get_subvolume_property.cache_clear()
  55. flexmock(module.borgmatic.execute).should_receive(
  56. 'execute_command_and_capture_output',
  57. ).and_return('thing=value').once()
  58. assert module.get_subvolume_property('btrfs', '/foo', 'thing') == 'value'
  59. assert module.get_subvolume_property('btrfs', '/foo', 'thing') == 'value'
  60. def test_get_containing_subvolume_path_with_subvolume_self_returns_it():
  61. flexmock(module).should_receive('path_is_a_subvolume').with_args(
  62. 'btrfs', '/foo/bar/baz'
  63. ).and_return(True)
  64. flexmock(module).should_receive('path_is_a_subvolume').with_args('btrfs', '/foo/bar').never()
  65. flexmock(module).should_receive('path_is_a_subvolume').with_args('btrfs', '/foo').never()
  66. flexmock(module).should_receive('path_is_a_subvolume').with_args('btrfs', '/').never()
  67. flexmock(module).should_receive('get_subvolume_property').and_return(False)
  68. assert module.get_containing_subvolume_path('btrfs', '/foo/bar/baz') == '/foo/bar/baz'
  69. def test_get_containing_subvolume_path_with_subvolume_parent_returns_it():
  70. flexmock(module).should_receive('path_is_a_subvolume').with_args(
  71. 'btrfs', '/foo/bar/baz'
  72. ).and_return(False)
  73. flexmock(module).should_receive('path_is_a_subvolume').with_args(
  74. 'btrfs', '/foo/bar'
  75. ).and_return(True)
  76. flexmock(module).should_receive('path_is_a_subvolume').with_args('btrfs', '/foo').never()
  77. flexmock(module).should_receive('path_is_a_subvolume').with_args('btrfs', '/').never()
  78. flexmock(module).should_receive('get_subvolume_property').and_return(False)
  79. assert module.get_containing_subvolume_path('btrfs', '/foo/bar/baz') == '/foo/bar'
  80. def test_get_containing_subvolume_path_with_subvolume_grandparent_returns_it():
  81. flexmock(module).should_receive('path_is_a_subvolume').with_args(
  82. 'btrfs', '/foo/bar/baz'
  83. ).and_return(False)
  84. flexmock(module).should_receive('path_is_a_subvolume').with_args(
  85. 'btrfs', '/foo/bar'
  86. ).and_return(False)
  87. flexmock(module).should_receive('path_is_a_subvolume').with_args('btrfs', '/foo').and_return(
  88. True
  89. )
  90. flexmock(module).should_receive('path_is_a_subvolume').with_args('btrfs', '/').never()
  91. flexmock(module).should_receive('get_subvolume_property').and_return(False)
  92. assert module.get_containing_subvolume_path('btrfs', '/foo/bar/baz') == '/foo'
  93. def test_get_containing_subvolume_path_without_subvolume_ancestor_returns_none():
  94. flexmock(module).should_receive('path_is_a_subvolume').with_args(
  95. 'btrfs', '/foo/bar/baz'
  96. ).and_return(False)
  97. flexmock(module).should_receive('path_is_a_subvolume').with_args(
  98. 'btrfs', '/foo/bar'
  99. ).and_return(False)
  100. flexmock(module).should_receive('path_is_a_subvolume').with_args('btrfs', '/foo').and_return(
  101. False
  102. )
  103. flexmock(module).should_receive('path_is_a_subvolume').with_args('btrfs', '/').and_return(False)
  104. flexmock(module).should_receive('get_subvolume_property').and_return(False)
  105. assert module.get_containing_subvolume_path('btrfs', '/foo/bar/baz') is None
  106. def test_get_containing_subvolume_path_with_read_only_subvolume_returns_none():
  107. flexmock(module).should_receive('path_is_a_subvolume').with_args(
  108. 'btrfs', '/foo/bar/baz'
  109. ).and_return(True)
  110. flexmock(module).should_receive('get_subvolume_property').and_return(True)
  111. assert module.get_containing_subvolume_path('btrfs', '/foo/bar/baz') is None
  112. def test_get_containing_subvolume_path_with_read_only_error_returns_none():
  113. flexmock(module).should_receive('path_is_a_subvolume').with_args(
  114. 'btrfs', '/foo/bar/baz'
  115. ).and_return(True)
  116. flexmock(module).should_receive('get_subvolume_property').and_raise(
  117. module.subprocess.CalledProcessError(1, 'wtf')
  118. )
  119. assert module.get_containing_subvolume_path('btrfs', '/foo/bar/baz') is None
  120. def test_get_all_subvolume_paths_skips_non_root_and_non_config_patterns():
  121. flexmock(module).should_receive('get_containing_subvolume_path').with_args(
  122. 'btrfs', '/foo'
  123. ).never()
  124. flexmock(module).should_receive('get_containing_subvolume_path').with_args(
  125. 'btrfs', '/bar'
  126. ).and_return('/bar').once()
  127. flexmock(module).should_receive('get_containing_subvolume_path').with_args(
  128. 'btrfs', '/baz'
  129. ).never()
  130. assert module.get_all_subvolume_paths(
  131. 'btrfs',
  132. (
  133. module.borgmatic.borg.pattern.Pattern(
  134. '/foo',
  135. type=module.borgmatic.borg.pattern.Pattern_type.ROOT,
  136. source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
  137. ),
  138. module.borgmatic.borg.pattern.Pattern(
  139. '/bar',
  140. type=module.borgmatic.borg.pattern.Pattern_type.ROOT,
  141. source=module.borgmatic.borg.pattern.Pattern_source.CONFIG,
  142. ),
  143. module.borgmatic.borg.pattern.Pattern(
  144. '/baz',
  145. type=module.borgmatic.borg.pattern.Pattern_type.INCLUDE,
  146. source=module.borgmatic.borg.pattern.Pattern_source.CONFIG,
  147. ),
  148. ),
  149. ) == ('/bar',)
  150. def test_get_all_subvolume_paths_skips_non_btrfs_patterns():
  151. flexmock(module).should_receive('get_containing_subvolume_path').with_args(
  152. 'btrfs', '/foo'
  153. ).and_return(None).once()
  154. flexmock(module).should_receive('get_containing_subvolume_path').with_args(
  155. 'btrfs', '/bar'
  156. ).and_return('/bar').once()
  157. assert module.get_all_subvolume_paths(
  158. 'btrfs',
  159. (
  160. module.borgmatic.borg.pattern.Pattern(
  161. '/foo',
  162. type=module.borgmatic.borg.pattern.Pattern_type.ROOT,
  163. source=module.borgmatic.borg.pattern.Pattern_source.CONFIG,
  164. ),
  165. module.borgmatic.borg.pattern.Pattern(
  166. '/bar',
  167. type=module.borgmatic.borg.pattern.Pattern_type.ROOT,
  168. source=module.borgmatic.borg.pattern.Pattern_source.CONFIG,
  169. ),
  170. ),
  171. ) == ('/bar',)
  172. def test_get_all_subvolume_paths_sorts_subvolume_paths():
  173. flexmock(module).should_receive('get_containing_subvolume_path').with_args(
  174. 'btrfs', '/foo'
  175. ).and_return('/foo').once()
  176. flexmock(module).should_receive('get_containing_subvolume_path').with_args(
  177. 'btrfs', '/bar'
  178. ).and_return('/bar').once()
  179. flexmock(module).should_receive('get_containing_subvolume_path').with_args(
  180. 'btrfs', '/baz'
  181. ).and_return('/baz').once()
  182. assert module.get_all_subvolume_paths(
  183. 'btrfs',
  184. (
  185. module.borgmatic.borg.pattern.Pattern(
  186. '/foo',
  187. type=module.borgmatic.borg.pattern.Pattern_type.ROOT,
  188. source=module.borgmatic.borg.pattern.Pattern_source.CONFIG,
  189. ),
  190. module.borgmatic.borg.pattern.Pattern(
  191. '/bar',
  192. type=module.borgmatic.borg.pattern.Pattern_type.ROOT,
  193. source=module.borgmatic.borg.pattern.Pattern_source.CONFIG,
  194. ),
  195. module.borgmatic.borg.pattern.Pattern(
  196. '/baz',
  197. type=module.borgmatic.borg.pattern.Pattern_type.ROOT,
  198. source=module.borgmatic.borg.pattern.Pattern_source.CONFIG,
  199. ),
  200. ),
  201. ) == ('/bar', '/baz', '/foo')
  202. def test_get_subvolumes_collects_subvolumes_matching_patterns():
  203. flexmock(module).should_receive('get_all_subvolume_paths').and_return(('/mnt1', '/mnt2'))
  204. contained_pattern = Pattern(
  205. '/mnt1',
  206. type=Pattern_type.ROOT,
  207. source=Pattern_source.CONFIG,
  208. )
  209. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  210. 'get_contained_patterns',
  211. ).with_args('/mnt1', object).and_return((contained_pattern,))
  212. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  213. 'get_contained_patterns',
  214. ).with_args('/mnt2', object).and_return(())
  215. assert module.get_subvolumes(
  216. 'btrfs',
  217. patterns=[
  218. Pattern('/mnt1'),
  219. Pattern('/mnt3'),
  220. ],
  221. ) == (module.Subvolume('/mnt1', contained_patterns=(contained_pattern,)),)
  222. def test_get_subvolumes_skips_non_root_patterns():
  223. flexmock(module).should_receive('get_all_subvolume_paths').and_return(('/mnt1', '/mnt2'))
  224. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  225. 'get_contained_patterns',
  226. ).with_args('/mnt1', object).and_return(
  227. (
  228. Pattern(
  229. '/mnt1',
  230. type=Pattern_type.EXCLUDE,
  231. source=Pattern_source.CONFIG,
  232. ),
  233. ),
  234. )
  235. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  236. 'get_contained_patterns',
  237. ).with_args('/mnt2', object).and_return(())
  238. assert (
  239. module.get_subvolumes(
  240. 'btrfs',
  241. patterns=[
  242. Pattern('/mnt1'),
  243. Pattern('/mnt3'),
  244. ],
  245. )
  246. == ()
  247. )
  248. def test_get_subvolumes_skips_non_config_patterns():
  249. flexmock(module).should_receive('get_all_subvolume_paths').and_return(('/mnt1', '/mnt2'))
  250. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  251. 'get_contained_patterns',
  252. ).with_args('/mnt1', object).and_return(
  253. (
  254. Pattern(
  255. '/mnt1',
  256. type=Pattern_type.ROOT,
  257. source=Pattern_source.HOOK,
  258. ),
  259. ),
  260. )
  261. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  262. 'get_contained_patterns',
  263. ).with_args('/mnt2', object).and_return(())
  264. assert (
  265. module.get_subvolumes(
  266. 'btrfs',
  267. patterns=[
  268. Pattern('/mnt1'),
  269. Pattern('/mnt3'),
  270. ],
  271. )
  272. == ()
  273. )
  274. @pytest.mark.parametrize(
  275. 'subvolume_path,expected_snapshot_path',
  276. (
  277. ('/foo/bar', '/foo/bar/.borgmatic-snapshot-1234/foo/bar'),
  278. ('/', '/.borgmatic-snapshot-1234'),
  279. ),
  280. )
  281. def test_make_snapshot_path_includes_stripped_subvolume_path(
  282. subvolume_path,
  283. expected_snapshot_path,
  284. ):
  285. flexmock(module.os).should_receive('getpid').and_return(1234)
  286. assert module.make_snapshot_path(subvolume_path) == expected_snapshot_path
  287. @pytest.mark.parametrize(
  288. 'subvolume_path,pattern,expected_pattern',
  289. (
  290. (
  291. '/foo/bar',
  292. Pattern('/foo/bar/baz'),
  293. Pattern('/foo/bar/.borgmatic-snapshot-1234/./foo/bar/baz'),
  294. ),
  295. ('/foo/bar', Pattern('/foo/bar'), Pattern('/foo/bar/.borgmatic-snapshot-1234/./foo/bar')),
  296. (
  297. '/foo/bar',
  298. Pattern('^/foo/bar', Pattern_type.INCLUDE, Pattern_style.REGULAR_EXPRESSION),
  299. Pattern(
  300. '^/foo/bar/.borgmatic-snapshot-1234/./foo/bar',
  301. Pattern_type.INCLUDE,
  302. Pattern_style.REGULAR_EXPRESSION,
  303. ),
  304. ),
  305. (
  306. '/foo/bar',
  307. Pattern('/foo/bar', Pattern_type.INCLUDE, Pattern_style.REGULAR_EXPRESSION),
  308. Pattern(
  309. '/foo/bar/.borgmatic-snapshot-1234/./foo/bar',
  310. Pattern_type.INCLUDE,
  311. Pattern_style.REGULAR_EXPRESSION,
  312. ),
  313. ),
  314. ('/', Pattern('/foo'), Pattern('/.borgmatic-snapshot-1234/./foo')),
  315. ('/', Pattern('/'), Pattern('/.borgmatic-snapshot-1234/./')),
  316. (
  317. '/foo/bar',
  318. Pattern('/foo/bar/./baz'),
  319. Pattern('/foo/bar/.borgmatic-snapshot-1234/foo/bar/./baz'),
  320. ),
  321. ),
  322. )
  323. def test_make_borg_snapshot_pattern_includes_slashdot_hack_and_stripped_pattern_path(
  324. subvolume_path,
  325. pattern,
  326. expected_pattern,
  327. ):
  328. flexmock(module.os).should_receive('getpid').and_return(1234)
  329. assert module.make_borg_snapshot_pattern(subvolume_path, pattern) == expected_pattern
  330. def test_dump_data_sources_snapshots_each_subvolume_and_replaces_patterns():
  331. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1'), Pattern('/mnt/subvol2')]
  332. config = {'btrfs': {}}
  333. flexmock(module).should_receive('get_subvolumes').and_return(
  334. (
  335. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  336. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  337. ),
  338. )
  339. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  340. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  341. )
  342. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  343. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  344. )
  345. flexmock(module).should_receive('snapshot_subvolume').with_args(
  346. 'btrfs',
  347. '/mnt/subvol1',
  348. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  349. ).once()
  350. flexmock(module).should_receive('snapshot_subvolume').with_args(
  351. 'btrfs',
  352. '/mnt/subvol2',
  353. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  354. ).once()
  355. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  356. '/mnt/subvol1',
  357. ).and_return(
  358. Pattern(
  359. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234',
  360. Pattern_type.NO_RECURSE,
  361. Pattern_style.FNMATCH,
  362. ),
  363. )
  364. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  365. '/mnt/subvol2',
  366. ).and_return(
  367. Pattern(
  368. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2/.borgmatic-snapshot-1234',
  369. Pattern_type.NO_RECURSE,
  370. Pattern_style.FNMATCH,
  371. ),
  372. )
  373. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  374. '/mnt/subvol1',
  375. object,
  376. ).and_return(Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1'))
  377. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  378. '/mnt/subvol2',
  379. object,
  380. ).and_return(Pattern('/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2'))
  381. flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
  382. object,
  383. Pattern('/mnt/subvol1'),
  384. module.borgmatic.borg.pattern.Pattern(
  385. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  386. source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
  387. ),
  388. ).once()
  389. flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
  390. object,
  391. Pattern('/mnt/subvol2'),
  392. module.borgmatic.borg.pattern.Pattern(
  393. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  394. source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
  395. ),
  396. ).once()
  397. flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
  398. object,
  399. module.borgmatic.borg.pattern.Pattern(
  400. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234',
  401. Pattern_type.NO_RECURSE,
  402. Pattern_style.FNMATCH,
  403. ),
  404. ).once()
  405. flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
  406. object,
  407. module.borgmatic.borg.pattern.Pattern(
  408. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2/.borgmatic-snapshot-1234',
  409. Pattern_type.NO_RECURSE,
  410. Pattern_style.FNMATCH,
  411. ),
  412. ).once()
  413. assert (
  414. module.dump_data_sources(
  415. hook_config=config['btrfs'],
  416. config=config,
  417. config_paths=('test.yaml',),
  418. borgmatic_runtime_directory='/run/borgmatic',
  419. patterns=patterns,
  420. dry_run=False,
  421. )
  422. == []
  423. )
  424. assert config == {
  425. 'btrfs': {},
  426. }
  427. def test_dump_data_sources_uses_custom_btrfs_command_in_commands():
  428. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  429. config = {'btrfs': {'btrfs_command': '/usr/local/bin/btrfs'}}
  430. flexmock(module).should_receive('get_subvolumes').and_return(
  431. (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),),
  432. )
  433. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  434. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  435. )
  436. flexmock(module).should_receive('snapshot_subvolume').with_args(
  437. '/usr/local/bin/btrfs',
  438. '/mnt/subvol1',
  439. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  440. ).once()
  441. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  442. '/mnt/subvol1',
  443. ).and_return(
  444. Pattern(
  445. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234',
  446. Pattern_type.NO_RECURSE,
  447. Pattern_style.FNMATCH,
  448. ),
  449. )
  450. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  451. '/mnt/subvol1',
  452. object,
  453. ).and_return(Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1'))
  454. flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
  455. object,
  456. Pattern('/mnt/subvol1'),
  457. module.borgmatic.borg.pattern.Pattern(
  458. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  459. source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
  460. ),
  461. ).once()
  462. flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
  463. object,
  464. module.borgmatic.borg.pattern.Pattern(
  465. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234',
  466. Pattern_type.NO_RECURSE,
  467. Pattern_style.FNMATCH,
  468. ),
  469. ).once()
  470. assert (
  471. module.dump_data_sources(
  472. hook_config=config['btrfs'],
  473. config=config,
  474. config_paths=('test.yaml',),
  475. borgmatic_runtime_directory='/run/borgmatic',
  476. patterns=patterns,
  477. dry_run=False,
  478. )
  479. == []
  480. )
  481. assert config == {
  482. 'btrfs': {
  483. 'btrfs_command': '/usr/local/bin/btrfs',
  484. },
  485. }
  486. def test_dump_data_sources_with_findmnt_command_warns():
  487. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  488. config = {'btrfs': {'findmnt_command': '/usr/local/bin/findmnt'}}
  489. flexmock(module.logger).should_receive('warning').once()
  490. flexmock(module).should_receive('get_subvolumes').with_args(
  491. 'btrfs',
  492. patterns,
  493. ).and_return(
  494. (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),),
  495. ).once()
  496. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  497. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  498. )
  499. flexmock(module).should_receive('snapshot_subvolume').with_args(
  500. 'btrfs',
  501. '/mnt/subvol1',
  502. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  503. ).once()
  504. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  505. '/mnt/subvol1',
  506. ).and_return(
  507. Pattern(
  508. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234',
  509. Pattern_type.NO_RECURSE,
  510. Pattern_style.FNMATCH,
  511. ),
  512. )
  513. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  514. '/mnt/subvol1',
  515. object,
  516. ).and_return(Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1'))
  517. flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
  518. object,
  519. Pattern('/mnt/subvol1'),
  520. module.borgmatic.borg.pattern.Pattern(
  521. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  522. source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
  523. ),
  524. ).once()
  525. flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
  526. object,
  527. module.borgmatic.borg.pattern.Pattern(
  528. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234',
  529. Pattern_type.NO_RECURSE,
  530. Pattern_style.FNMATCH,
  531. ),
  532. ).once()
  533. assert (
  534. module.dump_data_sources(
  535. hook_config=config['btrfs'],
  536. config=config,
  537. config_paths=('test.yaml',),
  538. borgmatic_runtime_directory='/run/borgmatic',
  539. patterns=patterns,
  540. dry_run=False,
  541. )
  542. == []
  543. )
  544. assert config == {
  545. 'btrfs': {
  546. 'findmnt_command': '/usr/local/bin/findmnt',
  547. },
  548. }
  549. def test_dump_data_sources_with_dry_run_skips_snapshot_and_patterns_update():
  550. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  551. config = {'btrfs': {}}
  552. flexmock(module).should_receive('get_subvolumes').and_return(
  553. (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),),
  554. )
  555. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  556. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  557. )
  558. flexmock(module).should_receive('snapshot_subvolume').never()
  559. flexmock(module).should_receive('make_snapshot_exclude_pattern').never()
  560. flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never()
  561. flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
  562. assert (
  563. module.dump_data_sources(
  564. hook_config=config['btrfs'],
  565. config=config,
  566. config_paths=('test.yaml',),
  567. borgmatic_runtime_directory='/run/borgmatic',
  568. patterns=patterns,
  569. dry_run=True,
  570. )
  571. == []
  572. )
  573. assert config == {'btrfs': {}}
  574. def test_dump_data_sources_without_matching_subvolumes_skips_snapshot_and_patterns_update():
  575. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  576. config = {'btrfs': {}}
  577. flexmock(module).should_receive('get_subvolumes').and_return(())
  578. flexmock(module).should_receive('make_snapshot_path').never()
  579. flexmock(module).should_receive('snapshot_subvolume').never()
  580. flexmock(module).should_receive('make_snapshot_exclude_pattern').never()
  581. flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never()
  582. flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
  583. assert (
  584. module.dump_data_sources(
  585. hook_config=config['btrfs'],
  586. config=config,
  587. config_paths=('test.yaml',),
  588. borgmatic_runtime_directory='/run/borgmatic',
  589. patterns=patterns,
  590. dry_run=False,
  591. )
  592. == []
  593. )
  594. assert config == {'btrfs': {}}
  595. def test_remove_data_source_dumps_deletes_snapshots():
  596. config = {'btrfs': {}}
  597. flexmock(module).should_receive('get_subvolumes').and_return(
  598. (
  599. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  600. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  601. ),
  602. )
  603. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  604. '/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1',
  605. )
  606. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  607. '/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2',
  608. )
  609. flexmock(module.borgmatic.config.paths).should_receive(
  610. 'replace_temporary_subdirectory_with_glob',
  611. ).with_args(
  612. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  613. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  614. ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
  615. flexmock(module.borgmatic.config.paths).should_receive(
  616. 'replace_temporary_subdirectory_with_glob',
  617. ).with_args(
  618. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  619. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  620. ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
  621. flexmock(module.glob).should_receive('glob').with_args(
  622. '/mnt/subvol1/.borgmatic-*/mnt/subvol1',
  623. ).and_return(
  624. (
  625. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  626. '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
  627. ),
  628. )
  629. flexmock(module.glob).should_receive('glob').with_args(
  630. '/mnt/subvol2/.borgmatic-*/mnt/subvol2',
  631. ).and_return(
  632. (
  633. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  634. '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
  635. ),
  636. )
  637. flexmock(module.os.path).should_receive('isdir').with_args(
  638. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  639. ).and_return(True)
  640. flexmock(module.os.path).should_receive('isdir').with_args(
  641. '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
  642. ).and_return(True)
  643. flexmock(module.os.path).should_receive('isdir').with_args(
  644. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  645. ).and_return(True)
  646. flexmock(module.os.path).should_receive('isdir').with_args(
  647. '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
  648. ).and_return(False)
  649. flexmock(module).should_receive('delete_snapshot').with_args(
  650. 'btrfs',
  651. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  652. ).once()
  653. flexmock(module).should_receive('delete_snapshot').with_args(
  654. 'btrfs',
  655. '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
  656. ).once()
  657. flexmock(module).should_receive('delete_snapshot').with_args(
  658. 'btrfs',
  659. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  660. ).once()
  661. flexmock(module).should_receive('delete_snapshot').with_args(
  662. 'btrfs',
  663. '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
  664. ).never()
  665. flexmock(module.os.path).should_receive('isdir').with_args(
  666. '/mnt/subvol1/.borgmatic-snapshot-1234',
  667. ).and_return(True)
  668. flexmock(module.os.path).should_receive('isdir').with_args(
  669. '/mnt/subvol1/.borgmatic-snapshot-5678',
  670. ).and_return(True)
  671. flexmock(module.os.path).should_receive('isdir').with_args(
  672. '/mnt/subvol2/.borgmatic-snapshot-1234',
  673. ).and_return(True)
  674. flexmock(module.os.path).should_receive('isdir').with_args(
  675. '/mnt/subvol2/.borgmatic-snapshot-5678',
  676. ).and_return(True)
  677. flexmock(module.shutil).should_receive('rmtree').with_args(
  678. '/mnt/subvol1/.borgmatic-snapshot-1234',
  679. ).once()
  680. flexmock(module.shutil).should_receive('rmtree').with_args(
  681. '/mnt/subvol1/.borgmatic-snapshot-5678',
  682. ).once()
  683. flexmock(module.shutil).should_receive('rmtree').with_args(
  684. '/mnt/subvol2/.borgmatic-snapshot-1234',
  685. ).once()
  686. flexmock(module.shutil).should_receive('rmtree').with_args(
  687. '/mnt/subvol2/.borgmatic-snapshot-5678',
  688. ).never()
  689. module.remove_data_source_dumps(
  690. hook_config=config['btrfs'],
  691. config=config,
  692. borgmatic_runtime_directory='/run/borgmatic',
  693. patterns=flexmock(),
  694. dry_run=False,
  695. )
  696. def test_remove_data_source_dumps_without_hook_configuration_bails():
  697. flexmock(module).should_receive('get_subvolumes').never()
  698. flexmock(module).should_receive('make_snapshot_path').never()
  699. flexmock(module.borgmatic.config.paths).should_receive(
  700. 'replace_temporary_subdirectory_with_glob',
  701. ).never()
  702. flexmock(module).should_receive('delete_snapshot').never()
  703. flexmock(module.shutil).should_receive('rmtree').never()
  704. module.remove_data_source_dumps(
  705. hook_config=None,
  706. config={'source_directories': '/mnt/subvolume'},
  707. borgmatic_runtime_directory='/run/borgmatic',
  708. patterns=flexmock(),
  709. dry_run=False,
  710. )
  711. def test_remove_data_source_dumps_with_get_subvolumes_file_not_found_error_bails():
  712. config = {'btrfs': {}}
  713. flexmock(module).should_receive('get_subvolumes').and_raise(FileNotFoundError)
  714. flexmock(module).should_receive('make_snapshot_path').never()
  715. flexmock(module.borgmatic.config.paths).should_receive(
  716. 'replace_temporary_subdirectory_with_glob',
  717. ).never()
  718. flexmock(module).should_receive('delete_snapshot').never()
  719. flexmock(module.shutil).should_receive('rmtree').never()
  720. module.remove_data_source_dumps(
  721. hook_config=config['btrfs'],
  722. config=config,
  723. borgmatic_runtime_directory='/run/borgmatic',
  724. patterns=flexmock(),
  725. dry_run=False,
  726. )
  727. def test_remove_data_source_dumps_with_get_subvolumes_called_process_error_bails():
  728. config = {'btrfs': {}}
  729. flexmock(module).should_receive('get_subvolumes').and_raise(
  730. module.subprocess.CalledProcessError(1, 'command', 'error'),
  731. )
  732. flexmock(module).should_receive('make_snapshot_path').never()
  733. flexmock(module.borgmatic.config.paths).should_receive(
  734. 'replace_temporary_subdirectory_with_glob',
  735. ).never()
  736. flexmock(module).should_receive('delete_snapshot').never()
  737. flexmock(module.shutil).should_receive('rmtree').never()
  738. module.remove_data_source_dumps(
  739. hook_config=config['btrfs'],
  740. config=config,
  741. borgmatic_runtime_directory='/run/borgmatic',
  742. patterns=flexmock(),
  743. dry_run=False,
  744. )
  745. def test_remove_data_source_dumps_with_dry_run_skips_deletes():
  746. config = {'btrfs': {}}
  747. flexmock(module).should_receive('get_subvolumes').and_return(
  748. (
  749. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  750. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  751. ),
  752. )
  753. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  754. '/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1',
  755. )
  756. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  757. '/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2',
  758. )
  759. flexmock(module.borgmatic.config.paths).should_receive(
  760. 'replace_temporary_subdirectory_with_glob',
  761. ).with_args(
  762. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  763. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  764. ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
  765. flexmock(module.borgmatic.config.paths).should_receive(
  766. 'replace_temporary_subdirectory_with_glob',
  767. ).with_args(
  768. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  769. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  770. ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
  771. flexmock(module.glob).should_receive('glob').with_args(
  772. '/mnt/subvol1/.borgmatic-*/mnt/subvol1',
  773. ).and_return(
  774. (
  775. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  776. '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
  777. ),
  778. )
  779. flexmock(module.glob).should_receive('glob').with_args(
  780. '/mnt/subvol2/.borgmatic-*/mnt/subvol2',
  781. ).and_return(
  782. (
  783. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  784. '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
  785. ),
  786. )
  787. flexmock(module.os.path).should_receive('isdir').with_args(
  788. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  789. ).and_return(True)
  790. flexmock(module.os.path).should_receive('isdir').with_args(
  791. '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
  792. ).and_return(True)
  793. flexmock(module.os.path).should_receive('isdir').with_args(
  794. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  795. ).and_return(True)
  796. flexmock(module.os.path).should_receive('isdir').with_args(
  797. '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
  798. ).and_return(False)
  799. flexmock(module).should_receive('delete_snapshot').never()
  800. flexmock(module.shutil).should_receive('rmtree').never()
  801. module.remove_data_source_dumps(
  802. hook_config=config['btrfs'],
  803. config=config,
  804. borgmatic_runtime_directory='/run/borgmatic',
  805. patterns=flexmock(),
  806. dry_run=True,
  807. )
  808. def test_remove_data_source_dumps_without_subvolumes_skips_deletes():
  809. config = {'btrfs': {}}
  810. flexmock(module).should_receive('get_subvolumes').and_return(())
  811. flexmock(module).should_receive('make_snapshot_path').never()
  812. flexmock(module.borgmatic.config.paths).should_receive(
  813. 'replace_temporary_subdirectory_with_glob',
  814. ).never()
  815. flexmock(module).should_receive('delete_snapshot').never()
  816. flexmock(module.shutil).should_receive('rmtree').never()
  817. module.remove_data_source_dumps(
  818. hook_config=config['btrfs'],
  819. config=config,
  820. borgmatic_runtime_directory='/run/borgmatic',
  821. patterns=flexmock(),
  822. dry_run=False,
  823. )
  824. def test_remove_data_source_without_snapshots_skips_deletes():
  825. config = {'btrfs': {}}
  826. flexmock(module).should_receive('get_subvolumes').and_return(
  827. (
  828. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  829. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  830. ),
  831. )
  832. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  833. '/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1',
  834. )
  835. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  836. '/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2',
  837. )
  838. flexmock(module.borgmatic.config.paths).should_receive(
  839. 'replace_temporary_subdirectory_with_glob',
  840. ).with_args(
  841. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  842. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  843. ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
  844. flexmock(module.borgmatic.config.paths).should_receive(
  845. 'replace_temporary_subdirectory_with_glob',
  846. ).with_args(
  847. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  848. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  849. ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
  850. flexmock(module.glob).should_receive('glob').and_return(())
  851. flexmock(module.os.path).should_receive('isdir').never()
  852. flexmock(module).should_receive('delete_snapshot').never()
  853. flexmock(module.shutil).should_receive('rmtree').never()
  854. module.remove_data_source_dumps(
  855. hook_config=config['btrfs'],
  856. config=config,
  857. borgmatic_runtime_directory='/run/borgmatic',
  858. patterns=flexmock(),
  859. dry_run=False,
  860. )
  861. def test_remove_data_source_dumps_with_delete_snapshot_file_not_found_error_bails():
  862. config = {'btrfs': {}}
  863. flexmock(module).should_receive('get_subvolumes').and_return(
  864. (
  865. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  866. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  867. ),
  868. )
  869. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  870. '/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1',
  871. )
  872. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  873. '/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2',
  874. )
  875. flexmock(module.borgmatic.config.paths).should_receive(
  876. 'replace_temporary_subdirectory_with_glob',
  877. ).with_args(
  878. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  879. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  880. ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
  881. flexmock(module.borgmatic.config.paths).should_receive(
  882. 'replace_temporary_subdirectory_with_glob',
  883. ).with_args(
  884. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  885. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  886. ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
  887. flexmock(module.glob).should_receive('glob').with_args(
  888. '/mnt/subvol1/.borgmatic-*/mnt/subvol1',
  889. ).and_return(
  890. (
  891. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  892. '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
  893. ),
  894. )
  895. flexmock(module.glob).should_receive('glob').with_args(
  896. '/mnt/subvol2/.borgmatic-*/mnt/subvol2',
  897. ).and_return(
  898. (
  899. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  900. '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
  901. ),
  902. )
  903. flexmock(module.os.path).should_receive('isdir').with_args(
  904. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  905. ).and_return(True)
  906. flexmock(module.os.path).should_receive('isdir').with_args(
  907. '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
  908. ).and_return(True)
  909. flexmock(module.os.path).should_receive('isdir').with_args(
  910. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  911. ).and_return(True)
  912. flexmock(module.os.path).should_receive('isdir').with_args(
  913. '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
  914. ).and_return(False)
  915. flexmock(module).should_receive('delete_snapshot').and_raise(FileNotFoundError)
  916. flexmock(module.shutil).should_receive('rmtree').never()
  917. module.remove_data_source_dumps(
  918. hook_config=config['btrfs'],
  919. config=config,
  920. borgmatic_runtime_directory='/run/borgmatic',
  921. patterns=flexmock(),
  922. dry_run=False,
  923. )
  924. def test_remove_data_source_dumps_with_delete_snapshot_called_process_error_bails():
  925. config = {'btrfs': {}}
  926. flexmock(module).should_receive('get_subvolumes').and_return(
  927. (
  928. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  929. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  930. ),
  931. )
  932. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  933. '/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1',
  934. )
  935. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  936. '/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2',
  937. )
  938. flexmock(module.borgmatic.config.paths).should_receive(
  939. 'replace_temporary_subdirectory_with_glob',
  940. ).with_args(
  941. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  942. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  943. ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
  944. flexmock(module.borgmatic.config.paths).should_receive(
  945. 'replace_temporary_subdirectory_with_glob',
  946. ).with_args(
  947. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  948. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  949. ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
  950. flexmock(module.glob).should_receive('glob').with_args(
  951. '/mnt/subvol1/.borgmatic-*/mnt/subvol1',
  952. ).and_return(
  953. (
  954. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  955. '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
  956. ),
  957. )
  958. flexmock(module.glob).should_receive('glob').with_args(
  959. '/mnt/subvol2/.borgmatic-*/mnt/subvol2',
  960. ).and_return(
  961. (
  962. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  963. '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
  964. ),
  965. )
  966. flexmock(module.os.path).should_receive('isdir').with_args(
  967. '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
  968. ).and_return(True)
  969. flexmock(module.os.path).should_receive('isdir').with_args(
  970. '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
  971. ).and_return(True)
  972. flexmock(module.os.path).should_receive('isdir').with_args(
  973. '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
  974. ).and_return(True)
  975. flexmock(module.os.path).should_receive('isdir').with_args(
  976. '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
  977. ).and_return(False)
  978. flexmock(module).should_receive('delete_snapshot').and_raise(
  979. module.subprocess.CalledProcessError(1, 'command', 'error'),
  980. )
  981. flexmock(module.shutil).should_receive('rmtree').never()
  982. module.remove_data_source_dumps(
  983. hook_config=config['btrfs'],
  984. config=config,
  985. borgmatic_runtime_directory='/run/borgmatic',
  986. patterns=flexmock(),
  987. dry_run=False,
  988. )
  989. def test_remove_data_source_dumps_with_root_subvolume_skips_duplicate_removal():
  990. config = {'btrfs': {}}
  991. flexmock(module).should_receive('get_subvolumes').and_return(
  992. (module.Subvolume('/', contained_patterns=(Pattern('/etc'),)),),
  993. )
  994. flexmock(module).should_receive('make_snapshot_path').with_args('/').and_return(
  995. '/.borgmatic-snapshot-1234',
  996. )
  997. flexmock(module.borgmatic.config.paths).should_receive(
  998. 'replace_temporary_subdirectory_with_glob',
  999. ).with_args(
  1000. '/.borgmatic-snapshot-1234',
  1001. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  1002. ).and_return('/.borgmatic-*')
  1003. flexmock(module.glob).should_receive('glob').with_args('/.borgmatic-*').and_return(
  1004. ('/.borgmatic-snapshot-1234', '/.borgmatic-snapshot-5678'),
  1005. )
  1006. flexmock(module.os.path).should_receive('isdir').with_args(
  1007. '/.borgmatic-snapshot-1234'
  1008. ).and_return(
  1009. True,
  1010. ).and_return(False)
  1011. flexmock(module.os.path).should_receive('isdir').with_args(
  1012. '/.borgmatic-snapshot-5678'
  1013. ).and_return(
  1014. True,
  1015. ).and_return(False)
  1016. flexmock(module).should_receive('delete_snapshot').with_args(
  1017. 'btrfs', '/.borgmatic-snapshot-1234'
  1018. ).once()
  1019. flexmock(module).should_receive('delete_snapshot').with_args(
  1020. 'btrfs', '/.borgmatic-snapshot-5678'
  1021. ).once()
  1022. flexmock(module.os.path).should_receive('isdir').with_args('').and_return(False)
  1023. flexmock(module.shutil).should_receive('rmtree').never()
  1024. module.remove_data_source_dumps(
  1025. hook_config=config['btrfs'],
  1026. config=config,
  1027. borgmatic_runtime_directory='/run/borgmatic',
  1028. patterns=flexmock(),
  1029. dry_run=False,
  1030. )