test_lvm.py 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394
  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 lvm as module
  5. def test_get_logical_volumes_filters_by_patterns():
  6. flexmock(module.borgmatic.execute).should_receive(
  7. 'execute_command_and_capture_output'
  8. ).and_return(
  9. '''
  10. {
  11. "blockdevices": [
  12. {
  13. "name": "vgroup-notmounted",
  14. "path": "/dev/mapper/vgroup-notmounted",
  15. "mountpoint": null,
  16. "type": "lvm"
  17. }, {
  18. "name": "vgroup-lvolume",
  19. "path": "/dev/mapper/vgroup-lvolume",
  20. "mountpoint": "/mnt/lvolume",
  21. "type": "lvm"
  22. }, {
  23. "name": "vgroup-other",
  24. "path": "/dev/mapper/vgroup-other",
  25. "mountpoint": "/mnt/other",
  26. "type": "lvm"
  27. }, {
  28. "name": "vgroup-notlvm",
  29. "path": "/dev/mapper/vgroup-notlvm",
  30. "mountpoint": "/mnt/notlvm",
  31. "type": "notlvm"
  32. }
  33. ]
  34. }
  35. '''
  36. )
  37. contained = {
  38. Pattern('/mnt/lvolume', source=Pattern_source.CONFIG),
  39. Pattern('/mnt/lvolume/subdir', source=Pattern_source.CONFIG),
  40. }
  41. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  42. 'get_contained_patterns'
  43. ).with_args(None, contained).never()
  44. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  45. 'get_contained_patterns'
  46. ).with_args('/mnt/lvolume', contained).and_return(
  47. (
  48. Pattern('/mnt/lvolume', source=Pattern_source.CONFIG),
  49. Pattern('/mnt/lvolume/subdir', source=Pattern_source.CONFIG),
  50. )
  51. )
  52. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  53. 'get_contained_patterns'
  54. ).with_args('/mnt/other', contained).and_return(())
  55. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  56. 'get_contained_patterns'
  57. ).with_args('/mnt/notlvm', contained).never()
  58. assert module.get_logical_volumes(
  59. 'lsblk',
  60. patterns=(
  61. Pattern('/mnt/lvolume', source=Pattern_source.CONFIG),
  62. Pattern('/mnt/lvolume/subdir', source=Pattern_source.CONFIG),
  63. ),
  64. ) == (
  65. module.Logical_volume(
  66. name='vgroup-lvolume',
  67. device_path='/dev/mapper/vgroup-lvolume',
  68. mount_point='/mnt/lvolume',
  69. contained_patterns=(
  70. Pattern('/mnt/lvolume', source=Pattern_source.CONFIG),
  71. Pattern('/mnt/lvolume/subdir', source=Pattern_source.CONFIG),
  72. ),
  73. ),
  74. )
  75. def test_get_logical_volumes_skips_non_root_patterns():
  76. flexmock(module.borgmatic.execute).should_receive(
  77. 'execute_command_and_capture_output'
  78. ).and_return(
  79. '''
  80. {
  81. "blockdevices": [
  82. {
  83. "name": "vgroup-lvolume",
  84. "path": "/dev/mapper/vgroup-lvolume",
  85. "mountpoint": "/mnt/lvolume",
  86. "type": "lvm"
  87. }
  88. ]
  89. }
  90. '''
  91. )
  92. contained = {
  93. Pattern('/mnt/lvolume', type=Pattern_type.EXCLUDE, source=Pattern_source.CONFIG),
  94. Pattern('/mnt/lvolume/subdir', type=Pattern_type.EXCLUDE, source=Pattern_source.CONFIG),
  95. }
  96. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  97. 'get_contained_patterns'
  98. ).with_args(None, contained).never()
  99. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  100. 'get_contained_patterns'
  101. ).with_args('/mnt/lvolume', contained).and_return(
  102. (
  103. Pattern('/mnt/lvolume', type=Pattern_type.EXCLUDE, source=Pattern_source.CONFIG),
  104. Pattern('/mnt/lvolume/subdir', type=Pattern_type.EXCLUDE, source=Pattern_source.CONFIG),
  105. )
  106. )
  107. assert (
  108. module.get_logical_volumes(
  109. 'lsblk',
  110. patterns=(
  111. Pattern('/mnt/lvolume', type=Pattern_type.EXCLUDE, source=Pattern_source.CONFIG),
  112. Pattern(
  113. '/mnt/lvolume/subdir', type=Pattern_type.EXCLUDE, source=Pattern_source.CONFIG
  114. ),
  115. ),
  116. )
  117. == ()
  118. )
  119. def test_get_logical_volumes_skips_non_config_patterns():
  120. flexmock(module.borgmatic.execute).should_receive(
  121. 'execute_command_and_capture_output'
  122. ).and_return(
  123. '''
  124. {
  125. "blockdevices": [
  126. {
  127. "name": "vgroup-lvolume",
  128. "path": "/dev/mapper/vgroup-lvolume",
  129. "mountpoint": "/mnt/lvolume",
  130. "type": "lvm"
  131. }
  132. ]
  133. }
  134. '''
  135. )
  136. contained = {
  137. Pattern('/mnt/lvolume', source=Pattern_source.HOOK),
  138. Pattern('/mnt/lvolume/subdir', source=Pattern_source.HOOK),
  139. }
  140. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  141. 'get_contained_patterns'
  142. ).with_args(None, contained).never()
  143. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  144. 'get_contained_patterns'
  145. ).with_args('/mnt/lvolume', contained).and_return(
  146. (
  147. Pattern('/mnt/lvolume', source=Pattern_source.HOOK),
  148. Pattern('/mnt/lvolume/subdir', source=Pattern_source.HOOK),
  149. )
  150. )
  151. assert (
  152. module.get_logical_volumes(
  153. 'lsblk',
  154. patterns=(
  155. Pattern('/mnt/lvolume', source=Pattern_source.HOOK),
  156. Pattern('/mnt/lvolume/subdir', source=Pattern_source.HOOK),
  157. ),
  158. )
  159. == ()
  160. )
  161. def test_get_logical_volumes_with_invalid_lsblk_json_errors():
  162. flexmock(module.borgmatic.execute).should_receive(
  163. 'execute_command_and_capture_output'
  164. ).and_return('{')
  165. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  166. 'get_contained_patterns'
  167. ).never()
  168. with pytest.raises(ValueError):
  169. module.get_logical_volumes(
  170. 'lsblk', patterns=(Pattern('/mnt/lvolume'), Pattern('/mnt/lvolume/subdir'))
  171. )
  172. def test_get_logical_volumes_with_lsblk_json_missing_keys_errors():
  173. flexmock(module.borgmatic.execute).should_receive(
  174. 'execute_command_and_capture_output'
  175. ).and_return('{"block_devices": [{}]}')
  176. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  177. 'get_contained_patterns'
  178. ).never()
  179. with pytest.raises(ValueError):
  180. module.get_logical_volumes(
  181. 'lsblk', patterns=(Pattern('/mnt/lvolume'), Pattern('/mnt/lvolume/subdir'))
  182. )
  183. def test_snapshot_logical_volume_with_percentage_snapshot_name_uses_lvcreate_extents_flag():
  184. flexmock(module.borgmatic.execute).should_receive('execute_command').with_args(
  185. (
  186. 'lvcreate',
  187. '--snapshot',
  188. '--extents',
  189. '10%ORIGIN',
  190. '--permission',
  191. 'r',
  192. '--name',
  193. 'snap',
  194. '/dev/snap',
  195. ),
  196. output_log_level=object,
  197. )
  198. module.snapshot_logical_volume('lvcreate', 'snap', '/dev/snap', '10%ORIGIN')
  199. def test_snapshot_logical_volume_with_non_percentage_snapshot_name_uses_lvcreate_size_flag():
  200. flexmock(module.borgmatic.execute).should_receive('execute_command').with_args(
  201. (
  202. 'lvcreate',
  203. '--snapshot',
  204. '--size',
  205. '10TB',
  206. '--permission',
  207. 'r',
  208. '--name',
  209. 'snap',
  210. '/dev/snap',
  211. ),
  212. output_log_level=object,
  213. )
  214. module.snapshot_logical_volume('lvcreate', 'snap', '/dev/snap', '10TB')
  215. @pytest.mark.parametrize(
  216. 'pattern,expected_pattern',
  217. (
  218. (
  219. Pattern('/foo/bar/baz'),
  220. Pattern('/run/borgmatic/lvm_snapshots/b33f/./foo/bar/baz'),
  221. ),
  222. (Pattern('/foo/bar'), Pattern('/run/borgmatic/lvm_snapshots/b33f/./foo/bar')),
  223. (
  224. Pattern('^/foo/bar', Pattern_type.INCLUDE, Pattern_style.REGULAR_EXPRESSION),
  225. Pattern(
  226. '^/run/borgmatic/lvm_snapshots/b33f/./foo/bar',
  227. Pattern_type.INCLUDE,
  228. Pattern_style.REGULAR_EXPRESSION,
  229. ),
  230. ),
  231. (
  232. Pattern('/foo/bar', Pattern_type.INCLUDE, Pattern_style.REGULAR_EXPRESSION),
  233. Pattern(
  234. '/run/borgmatic/lvm_snapshots/b33f/./foo/bar',
  235. Pattern_type.INCLUDE,
  236. Pattern_style.REGULAR_EXPRESSION,
  237. ),
  238. ),
  239. (Pattern('/foo'), Pattern('/run/borgmatic/lvm_snapshots/b33f/./foo')),
  240. (Pattern('/'), Pattern('/run/borgmatic/lvm_snapshots/b33f/./')),
  241. ),
  242. )
  243. def test_make_borg_snapshot_pattern_includes_slashdot_hack_and_stripped_pattern_path(
  244. pattern, expected_pattern
  245. ):
  246. flexmock(module.hashlib).should_receive('shake_256').and_return(
  247. flexmock(hexdigest=lambda length: 'b33f')
  248. )
  249. assert (
  250. module.make_borg_snapshot_pattern(
  251. pattern, flexmock(mount_point='/something'), '/run/borgmatic'
  252. )
  253. == expected_pattern
  254. )
  255. def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
  256. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  257. flexmock()
  258. )
  259. config = {'lvm': {}}
  260. patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  261. logical_volumes = (
  262. module.Logical_volume(
  263. name='lvolume1',
  264. device_path='/dev/lvolume1',
  265. mount_point='/mnt/lvolume1',
  266. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  267. ),
  268. module.Logical_volume(
  269. name='lvolume2',
  270. device_path='/dev/lvolume2',
  271. mount_point='/mnt/lvolume2',
  272. contained_patterns=(Pattern('/mnt/lvolume2'),),
  273. ),
  274. )
  275. flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes)
  276. flexmock(module.os).should_receive('getpid').and_return(1234)
  277. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  278. 'lvcreate', 'lvolume1_borgmatic-1234', '/dev/lvolume1', module.DEFAULT_SNAPSHOT_SIZE
  279. ).once()
  280. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  281. 'lvcreate', 'lvolume2_borgmatic-1234', '/dev/lvolume2', module.DEFAULT_SNAPSHOT_SIZE
  282. ).once()
  283. flexmock(module).should_receive('get_snapshots').with_args(
  284. 'lvs', snapshot_name='lvolume1_borgmatic-1234'
  285. ).and_return(
  286. (module.Snapshot(name='lvolume1_borgmatic-1234', device_path='/dev/lvolume1_snap'),)
  287. )
  288. flexmock(module).should_receive('get_snapshots').with_args(
  289. 'lvs', snapshot_name='lvolume2_borgmatic-1234'
  290. ).and_return(
  291. (module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
  292. )
  293. flexmock(module.hashlib).should_receive('shake_256').and_return(
  294. flexmock(hexdigest=lambda length: 'b33f')
  295. )
  296. flexmock(module).should_receive('mount_snapshot').with_args(
  297. 'mount', '/dev/lvolume1_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  298. ).once()
  299. flexmock(module).should_receive('mount_snapshot').with_args(
  300. 'mount', '/dev/lvolume2_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  301. ).once()
  302. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  303. Pattern('/mnt/lvolume1/subdir'), logical_volumes[0], '/run/borgmatic'
  304. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'))
  305. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  306. Pattern('/mnt/lvolume2'), logical_volumes[1], '/run/borgmatic'
  307. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
  308. assert (
  309. module.dump_data_sources(
  310. hook_config=config['lvm'],
  311. config=config,
  312. config_paths=('test.yaml',),
  313. borgmatic_runtime_directory='/run/borgmatic',
  314. patterns=patterns,
  315. dry_run=False,
  316. )
  317. == []
  318. )
  319. assert patterns == [
  320. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
  321. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
  322. ]
  323. def test_dump_data_sources_with_no_logical_volumes_skips_snapshots():
  324. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  325. flexmock()
  326. )
  327. config = {'lvm': {}}
  328. patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  329. flexmock(module).should_receive('get_logical_volumes').and_return(())
  330. flexmock(module).should_receive('snapshot_logical_volume').never()
  331. flexmock(module).should_receive('mount_snapshot').never()
  332. assert (
  333. module.dump_data_sources(
  334. hook_config=config['lvm'],
  335. config=config,
  336. config_paths=('test.yaml',),
  337. borgmatic_runtime_directory='/run/borgmatic',
  338. patterns=patterns,
  339. dry_run=False,
  340. )
  341. == []
  342. )
  343. assert patterns == [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  344. def test_dump_data_sources_uses_snapshot_size_for_snapshot():
  345. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  346. flexmock()
  347. )
  348. config = {'lvm': {'snapshot_size': '1000PB'}}
  349. patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  350. logical_volumes = (
  351. module.Logical_volume(
  352. name='lvolume1',
  353. device_path='/dev/lvolume1',
  354. mount_point='/mnt/lvolume1',
  355. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  356. ),
  357. module.Logical_volume(
  358. name='lvolume2',
  359. device_path='/dev/lvolume2',
  360. mount_point='/mnt/lvolume2',
  361. contained_patterns=(Pattern('/mnt/lvolume2'),),
  362. ),
  363. )
  364. flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes)
  365. flexmock(module.os).should_receive('getpid').and_return(1234)
  366. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  367. 'lvcreate',
  368. 'lvolume1_borgmatic-1234',
  369. '/dev/lvolume1',
  370. '1000PB',
  371. ).once()
  372. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  373. 'lvcreate',
  374. 'lvolume2_borgmatic-1234',
  375. '/dev/lvolume2',
  376. '1000PB',
  377. ).once()
  378. flexmock(module).should_receive('get_snapshots').with_args(
  379. 'lvs', snapshot_name='lvolume1_borgmatic-1234'
  380. ).and_return(
  381. (module.Snapshot(name='lvolume1_borgmatic-1234', device_path='/dev/lvolume1_snap'),)
  382. )
  383. flexmock(module).should_receive('get_snapshots').with_args(
  384. 'lvs', snapshot_name='lvolume2_borgmatic-1234'
  385. ).and_return(
  386. (module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
  387. )
  388. flexmock(module.hashlib).should_receive('shake_256').and_return(
  389. flexmock(hexdigest=lambda length: 'b33f')
  390. )
  391. flexmock(module).should_receive('mount_snapshot').with_args(
  392. 'mount', '/dev/lvolume1_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  393. ).once()
  394. flexmock(module).should_receive('mount_snapshot').with_args(
  395. 'mount', '/dev/lvolume2_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  396. ).once()
  397. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  398. Pattern('/mnt/lvolume1/subdir'), logical_volumes[0], '/run/borgmatic'
  399. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'))
  400. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  401. Pattern('/mnt/lvolume2'), logical_volumes[1], '/run/borgmatic'
  402. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
  403. assert (
  404. module.dump_data_sources(
  405. hook_config=config['lvm'],
  406. config=config,
  407. config_paths=('test.yaml',),
  408. borgmatic_runtime_directory='/run/borgmatic',
  409. patterns=patterns,
  410. dry_run=False,
  411. )
  412. == []
  413. )
  414. assert patterns == [
  415. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
  416. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
  417. ]
  418. def test_dump_data_sources_uses_custom_commands():
  419. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  420. flexmock()
  421. )
  422. config = {
  423. 'lvm': {
  424. 'lsblk_command': '/usr/local/bin/lsblk',
  425. 'lvcreate_command': '/usr/local/bin/lvcreate',
  426. 'lvs_command': '/usr/local/bin/lvs',
  427. 'mount_command': '/usr/local/bin/mount',
  428. },
  429. }
  430. patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  431. logical_volumes = (
  432. module.Logical_volume(
  433. name='lvolume1',
  434. device_path='/dev/lvolume1',
  435. mount_point='/mnt/lvolume1',
  436. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  437. ),
  438. module.Logical_volume(
  439. name='lvolume2',
  440. device_path='/dev/lvolume2',
  441. mount_point='/mnt/lvolume2',
  442. contained_patterns=(Pattern('/mnt/lvolume2'),),
  443. ),
  444. )
  445. flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes)
  446. flexmock(module.os).should_receive('getpid').and_return(1234)
  447. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  448. '/usr/local/bin/lvcreate',
  449. 'lvolume1_borgmatic-1234',
  450. '/dev/lvolume1',
  451. module.DEFAULT_SNAPSHOT_SIZE,
  452. ).once()
  453. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  454. '/usr/local/bin/lvcreate',
  455. 'lvolume2_borgmatic-1234',
  456. '/dev/lvolume2',
  457. module.DEFAULT_SNAPSHOT_SIZE,
  458. ).once()
  459. flexmock(module).should_receive('get_snapshots').with_args(
  460. '/usr/local/bin/lvs', snapshot_name='lvolume1_borgmatic-1234'
  461. ).and_return(
  462. (module.Snapshot(name='lvolume1_borgmatic-1234', device_path='/dev/lvolume1_snap'),)
  463. )
  464. flexmock(module).should_receive('get_snapshots').with_args(
  465. '/usr/local/bin/lvs', snapshot_name='lvolume2_borgmatic-1234'
  466. ).and_return(
  467. (module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
  468. )
  469. flexmock(module.hashlib).should_receive('shake_256').and_return(
  470. flexmock(hexdigest=lambda length: 'b33f')
  471. )
  472. flexmock(module).should_receive('mount_snapshot').with_args(
  473. '/usr/local/bin/mount',
  474. '/dev/lvolume1_snap',
  475. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  476. ).once()
  477. flexmock(module).should_receive('mount_snapshot').with_args(
  478. '/usr/local/bin/mount',
  479. '/dev/lvolume2_snap',
  480. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  481. ).once()
  482. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  483. Pattern('/mnt/lvolume1/subdir'), logical_volumes[0], '/run/borgmatic'
  484. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'))
  485. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  486. Pattern('/mnt/lvolume2'), logical_volumes[1], '/run/borgmatic'
  487. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
  488. assert (
  489. module.dump_data_sources(
  490. hook_config=config['lvm'],
  491. config=config,
  492. config_paths=('test.yaml',),
  493. borgmatic_runtime_directory='/run/borgmatic',
  494. patterns=patterns,
  495. dry_run=False,
  496. )
  497. == []
  498. )
  499. assert patterns == [
  500. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
  501. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
  502. ]
  503. def test_dump_data_sources_with_dry_run_skips_snapshots_and_does_not_touch_patterns():
  504. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  505. flexmock()
  506. )
  507. config = {'lvm': {}}
  508. patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  509. flexmock(module).should_receive('get_logical_volumes').and_return(
  510. (
  511. module.Logical_volume(
  512. name='lvolume1',
  513. device_path='/dev/lvolume1',
  514. mount_point='/mnt/lvolume1',
  515. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  516. ),
  517. module.Logical_volume(
  518. name='lvolume2',
  519. device_path='/dev/lvolume2',
  520. mount_point='/mnt/lvolume2',
  521. contained_patterns=(Pattern('/mnt/lvolume2'),),
  522. ),
  523. )
  524. )
  525. flexmock(module.os).should_receive('getpid').and_return(1234)
  526. flexmock(module).should_receive('snapshot_logical_volume').never()
  527. flexmock(module).should_receive('get_snapshots').with_args(
  528. 'lvs', snapshot_name='lvolume1_borgmatic-1234'
  529. ).and_return(
  530. (module.Snapshot(name='lvolume1_borgmatic-1234', device_path='/dev/lvolume1_snap'),)
  531. )
  532. flexmock(module).should_receive('get_snapshots').with_args(
  533. 'lvs', snapshot_name='lvolume2_borgmatic-1234'
  534. ).and_return(
  535. (module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
  536. )
  537. flexmock(module).should_receive('mount_snapshot').never()
  538. assert (
  539. module.dump_data_sources(
  540. hook_config=config['lvm'],
  541. config=config,
  542. config_paths=('test.yaml',),
  543. borgmatic_runtime_directory='/run/borgmatic',
  544. patterns=patterns,
  545. dry_run=True,
  546. )
  547. == []
  548. )
  549. assert patterns == [
  550. Pattern('/mnt/lvolume1/subdir'),
  551. Pattern('/mnt/lvolume2'),
  552. ]
  553. def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained_patterns():
  554. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  555. flexmock()
  556. )
  557. config = {'lvm': {}}
  558. patterns = [Pattern('/hmm')]
  559. logical_volumes = (
  560. module.Logical_volume(
  561. name='lvolume1',
  562. device_path='/dev/lvolume1',
  563. mount_point='/mnt/lvolume1',
  564. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  565. ),
  566. module.Logical_volume(
  567. name='lvolume2',
  568. device_path='/dev/lvolume2',
  569. mount_point='/mnt/lvolume2',
  570. contained_patterns=(Pattern('/mnt/lvolume2'),),
  571. ),
  572. )
  573. flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes)
  574. flexmock(module.os).should_receive('getpid').and_return(1234)
  575. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  576. 'lvcreate', 'lvolume1_borgmatic-1234', '/dev/lvolume1', module.DEFAULT_SNAPSHOT_SIZE
  577. ).once()
  578. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  579. 'lvcreate', 'lvolume2_borgmatic-1234', '/dev/lvolume2', module.DEFAULT_SNAPSHOT_SIZE
  580. ).once()
  581. flexmock(module).should_receive('get_snapshots').with_args(
  582. 'lvs', snapshot_name='lvolume1_borgmatic-1234'
  583. ).and_return(
  584. (module.Snapshot(name='lvolume1_borgmatic-1234', device_path='/dev/lvolume1_snap'),)
  585. )
  586. flexmock(module).should_receive('get_snapshots').with_args(
  587. 'lvs', snapshot_name='lvolume2_borgmatic-1234'
  588. ).and_return(
  589. (module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
  590. )
  591. flexmock(module.hashlib).should_receive('shake_256').and_return(
  592. flexmock(hexdigest=lambda length: 'b33f')
  593. )
  594. flexmock(module).should_receive('mount_snapshot').with_args(
  595. 'mount', '/dev/lvolume1_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  596. ).once()
  597. flexmock(module).should_receive('mount_snapshot').with_args(
  598. 'mount', '/dev/lvolume2_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  599. ).once()
  600. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  601. Pattern('/mnt/lvolume1/subdir'), logical_volumes[0], '/run/borgmatic'
  602. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'))
  603. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  604. Pattern('/mnt/lvolume2'), logical_volumes[1], '/run/borgmatic'
  605. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
  606. assert (
  607. module.dump_data_sources(
  608. hook_config=config['lvm'],
  609. config=config,
  610. config_paths=('test.yaml',),
  611. borgmatic_runtime_directory='/run/borgmatic',
  612. patterns=patterns,
  613. dry_run=False,
  614. )
  615. == []
  616. )
  617. assert patterns == [
  618. Pattern('/hmm'),
  619. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
  620. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
  621. ]
  622. def test_dump_data_sources_with_missing_snapshot_errors():
  623. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  624. flexmock()
  625. )
  626. config = {'lvm': {}}
  627. patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  628. flexmock(module).should_receive('get_logical_volumes').and_return(
  629. (
  630. module.Logical_volume(
  631. name='lvolume1',
  632. device_path='/dev/lvolume1',
  633. mount_point='/mnt/lvolume1',
  634. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  635. ),
  636. module.Logical_volume(
  637. name='lvolume2',
  638. device_path='/dev/lvolume2',
  639. mount_point='/mnt/lvolume2',
  640. contained_patterns=(Pattern('/mnt/lvolume2'),),
  641. ),
  642. )
  643. )
  644. flexmock(module.os).should_receive('getpid').and_return(1234)
  645. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  646. 'lvcreate', 'lvolume1_borgmatic-1234', '/dev/lvolume1', module.DEFAULT_SNAPSHOT_SIZE
  647. ).once()
  648. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  649. 'lvcreate', 'lvolume2_borgmatic-1234', '/dev/lvolume2', module.DEFAULT_SNAPSHOT_SIZE
  650. ).never()
  651. flexmock(module).should_receive('get_snapshots').with_args(
  652. 'lvs', snapshot_name='lvolume1_borgmatic-1234'
  653. ).and_return(())
  654. flexmock(module).should_receive('get_snapshots').with_args(
  655. 'lvs', snapshot_name='lvolume2_borgmatic-1234'
  656. ).never()
  657. flexmock(module).should_receive('mount_snapshot').never()
  658. with pytest.raises(ValueError):
  659. module.dump_data_sources(
  660. hook_config=config['lvm'],
  661. config=config,
  662. config_paths=('test.yaml',),
  663. borgmatic_runtime_directory='/run/borgmatic',
  664. patterns=patterns,
  665. dry_run=False,
  666. )
  667. def test_get_snapshots_lists_all_snapshots():
  668. flexmock(module.borgmatic.execute).should_receive(
  669. 'execute_command_and_capture_output'
  670. ).and_return(
  671. '''
  672. {
  673. "report": [
  674. {
  675. "lv": [
  676. {"lv_name": "snap1", "lv_path": "/dev/snap1"},
  677. {"lv_name": "snap2", "lv_path": "/dev/snap2"}
  678. ]
  679. }
  680. ],
  681. "log": [
  682. ]
  683. }
  684. '''
  685. )
  686. assert module.get_snapshots('lvs') == (
  687. module.Snapshot('snap1', '/dev/snap1'),
  688. module.Snapshot('snap2', '/dev/snap2'),
  689. )
  690. def test_get_snapshots_with_snapshot_name_lists_just_that_snapshot():
  691. flexmock(module.borgmatic.execute).should_receive(
  692. 'execute_command_and_capture_output'
  693. ).and_return(
  694. '''
  695. {
  696. "report": [
  697. {
  698. "lv": [
  699. {"lv_name": "snap1", "lv_path": "/dev/snap1"},
  700. {"lv_name": "snap2", "lv_path": "/dev/snap2"}
  701. ]
  702. }
  703. ],
  704. "log": [
  705. ]
  706. }
  707. '''
  708. )
  709. assert module.get_snapshots('lvs', snapshot_name='snap2') == (
  710. module.Snapshot('snap2', '/dev/snap2'),
  711. )
  712. def test_get_snapshots_with_invalid_lvs_json_errors():
  713. flexmock(module.borgmatic.execute).should_receive(
  714. 'execute_command_and_capture_output'
  715. ).and_return('{')
  716. with pytest.raises(ValueError):
  717. assert module.get_snapshots('lvs')
  718. def test_get_snapshots_with_lvs_json_missing_report_errors():
  719. flexmock(module.borgmatic.execute).should_receive(
  720. 'execute_command_and_capture_output'
  721. ).and_return(
  722. '''
  723. {
  724. "report": [],
  725. "log": [
  726. ]
  727. }
  728. '''
  729. )
  730. with pytest.raises(ValueError):
  731. assert module.get_snapshots('lvs')
  732. def test_get_snapshots_with_lvs_json_missing_keys_errors():
  733. flexmock(module.borgmatic.execute).should_receive(
  734. 'execute_command_and_capture_output'
  735. ).and_return(
  736. '''
  737. {
  738. "report": [
  739. {
  740. "lv": [
  741. {}
  742. ]
  743. }
  744. ],
  745. "log": [
  746. ]
  747. }
  748. '''
  749. )
  750. with pytest.raises(ValueError):
  751. assert module.get_snapshots('lvs')
  752. def test_remove_data_source_dumps_unmounts_and_remove_snapshots():
  753. config = {'lvm': {}}
  754. flexmock(module).should_receive('get_logical_volumes').and_return(
  755. (
  756. module.Logical_volume(
  757. name='lvolume1',
  758. device_path='/dev/lvolume1',
  759. mount_point='/mnt/lvolume1',
  760. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  761. ),
  762. module.Logical_volume(
  763. name='lvolume2',
  764. device_path='/dev/lvolume2',
  765. mount_point='/mnt/lvolume2',
  766. contained_patterns=(Pattern('/mnt/lvolume2'),),
  767. ),
  768. )
  769. )
  770. flexmock(module.borgmatic.config.paths).should_receive(
  771. 'replace_temporary_subdirectory_with_glob'
  772. ).and_return('/run/borgmatic')
  773. flexmock(module.glob).should_receive('glob').replace_with(
  774. lambda path: [path.replace('*', 'b33f')]
  775. )
  776. flexmock(module.os.path).should_receive('isdir').and_return(True)
  777. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  778. flexmock(module.shutil).should_receive('rmtree')
  779. flexmock(module).should_receive('unmount_snapshot').with_args(
  780. 'umount',
  781. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  782. ).once()
  783. flexmock(module).should_receive('unmount_snapshot').with_args(
  784. 'umount',
  785. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  786. ).once()
  787. flexmock(module).should_receive('get_snapshots').and_return(
  788. (
  789. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  790. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  791. module.Snapshot('nonborgmatic', '/dev/nonborgmatic'),
  792. ),
  793. )
  794. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  795. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  796. flexmock(module).should_receive('remove_snapshot').with_args(
  797. 'nonborgmatic', '/dev/nonborgmatic'
  798. ).never()
  799. module.remove_data_source_dumps(
  800. hook_config=config['lvm'],
  801. config=config,
  802. borgmatic_runtime_directory='/run/borgmatic',
  803. dry_run=False,
  804. )
  805. def test_remove_data_source_dumps_bails_for_missing_lvm_configuration():
  806. flexmock(module).should_receive('get_logical_volumes').never()
  807. flexmock(module.borgmatic.config.paths).should_receive(
  808. 'replace_temporary_subdirectory_with_glob'
  809. ).never()
  810. flexmock(module).should_receive('unmount_snapshot').never()
  811. flexmock(module).should_receive('remove_snapshot').never()
  812. module.remove_data_source_dumps(
  813. hook_config=None,
  814. config={'source_directories': '/mnt/lvolume'},
  815. borgmatic_runtime_directory='/run/borgmatic',
  816. dry_run=False,
  817. )
  818. def test_remove_data_source_dumps_bails_for_missing_lsblk_command():
  819. config = {'lvm': {}}
  820. flexmock(module).should_receive('get_logical_volumes').and_raise(FileNotFoundError)
  821. flexmock(module.borgmatic.config.paths).should_receive(
  822. 'replace_temporary_subdirectory_with_glob'
  823. ).never()
  824. flexmock(module).should_receive('unmount_snapshot').never()
  825. flexmock(module).should_receive('remove_snapshot').never()
  826. module.remove_data_source_dumps(
  827. hook_config=config['lvm'],
  828. config=config,
  829. borgmatic_runtime_directory='/run/borgmatic',
  830. dry_run=False,
  831. )
  832. def test_remove_data_source_dumps_bails_for_lsblk_command_error():
  833. config = {'lvm': {}}
  834. flexmock(module).should_receive('get_logical_volumes').and_raise(
  835. module.subprocess.CalledProcessError(1, 'wtf')
  836. )
  837. flexmock(module.borgmatic.config.paths).should_receive(
  838. 'replace_temporary_subdirectory_with_glob'
  839. ).never()
  840. flexmock(module).should_receive('unmount_snapshot').never()
  841. flexmock(module).should_receive('remove_snapshot').never()
  842. module.remove_data_source_dumps(
  843. hook_config=config['lvm'],
  844. config=config,
  845. borgmatic_runtime_directory='/run/borgmatic',
  846. dry_run=False,
  847. )
  848. def test_remove_data_source_dumps_with_missing_snapshot_directory_skips_unmount():
  849. config = {'lvm': {}}
  850. flexmock(module).should_receive('get_logical_volumes').and_return(
  851. (
  852. module.Logical_volume(
  853. name='lvolume1',
  854. device_path='/dev/lvolume1',
  855. mount_point='/mnt/lvolume1',
  856. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  857. ),
  858. module.Logical_volume(
  859. name='lvolume2',
  860. device_path='/dev/lvolume2',
  861. mount_point='/mnt/lvolume2',
  862. contained_patterns=(Pattern('/mnt/lvolume2'),),
  863. ),
  864. )
  865. )
  866. flexmock(module.borgmatic.config.paths).should_receive(
  867. 'replace_temporary_subdirectory_with_glob'
  868. ).and_return('/run/borgmatic')
  869. flexmock(module.glob).should_receive('glob').replace_with(
  870. lambda path: [path.replace('*', 'b33f')]
  871. )
  872. flexmock(module.os.path).should_receive('isdir').with_args(
  873. '/run/borgmatic/lvm_snapshots/b33f'
  874. ).and_return(False)
  875. flexmock(module.shutil).should_receive('rmtree').never()
  876. flexmock(module).should_receive('unmount_snapshot').never()
  877. flexmock(module).should_receive('get_snapshots').and_return(
  878. (
  879. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  880. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  881. ),
  882. )
  883. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  884. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  885. module.remove_data_source_dumps(
  886. hook_config=config['lvm'],
  887. config=config,
  888. borgmatic_runtime_directory='/run/borgmatic',
  889. dry_run=False,
  890. )
  891. def test_remove_data_source_dumps_with_missing_snapshot_mount_path_skips_unmount():
  892. config = {'lvm': {}}
  893. flexmock(module).should_receive('get_logical_volumes').and_return(
  894. (
  895. module.Logical_volume(
  896. name='lvolume1',
  897. device_path='/dev/lvolume1',
  898. mount_point='/mnt/lvolume1',
  899. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  900. ),
  901. module.Logical_volume(
  902. name='lvolume2',
  903. device_path='/dev/lvolume2',
  904. mount_point='/mnt/lvolume2',
  905. contained_patterns=(Pattern('/mnt/lvolume2'),),
  906. ),
  907. )
  908. )
  909. flexmock(module.borgmatic.config.paths).should_receive(
  910. 'replace_temporary_subdirectory_with_glob'
  911. ).and_return('/run/borgmatic')
  912. flexmock(module.glob).should_receive('glob').replace_with(
  913. lambda path: [path.replace('*', 'b33f')]
  914. )
  915. flexmock(module.os.path).should_receive('isdir').with_args(
  916. '/run/borgmatic/lvm_snapshots/b33f'
  917. ).and_return(True)
  918. flexmock(module.os.path).should_receive('isdir').with_args(
  919. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  920. ).and_return(False)
  921. flexmock(module.os.path).should_receive('isdir').with_args(
  922. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  923. ).and_return(True)
  924. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  925. flexmock(module.shutil).should_receive('rmtree')
  926. flexmock(module).should_receive('unmount_snapshot').with_args(
  927. 'umount',
  928. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  929. ).never()
  930. flexmock(module).should_receive('unmount_snapshot').with_args(
  931. 'umount',
  932. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  933. ).once()
  934. flexmock(module).should_receive('get_snapshots').and_return(
  935. (
  936. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  937. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  938. ),
  939. )
  940. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  941. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  942. module.remove_data_source_dumps(
  943. hook_config=config['lvm'],
  944. config=config,
  945. borgmatic_runtime_directory='/run/borgmatic',
  946. dry_run=False,
  947. )
  948. def test_remove_data_source_dumps_with_empty_snapshot_mount_path_skips_unmount():
  949. config = {'lvm': {}}
  950. flexmock(module).should_receive('get_logical_volumes').and_return(
  951. (
  952. module.Logical_volume(
  953. name='lvolume1',
  954. device_path='/dev/lvolume1',
  955. mount_point='/mnt/lvolume1',
  956. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  957. ),
  958. module.Logical_volume(
  959. name='lvolume2',
  960. device_path='/dev/lvolume2',
  961. mount_point='/mnt/lvolume2',
  962. contained_patterns=(Pattern('/mnt/lvolume2'),),
  963. ),
  964. )
  965. )
  966. flexmock(module.borgmatic.config.paths).should_receive(
  967. 'replace_temporary_subdirectory_with_glob'
  968. ).and_return('/run/borgmatic')
  969. flexmock(module.glob).should_receive('glob').replace_with(
  970. lambda path: [path.replace('*', 'b33f')]
  971. )
  972. flexmock(module.os.path).should_receive('isdir').with_args(
  973. '/run/borgmatic/lvm_snapshots/b33f'
  974. ).and_return(True)
  975. flexmock(module.os.path).should_receive('isdir').with_args(
  976. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  977. ).and_return(True)
  978. flexmock(module.os).should_receive('listdir').with_args(
  979. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  980. ).and_return([])
  981. flexmock(module.os.path).should_receive('isdir').with_args(
  982. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  983. ).and_return(True)
  984. flexmock(module.os).should_receive('listdir').with_args(
  985. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  986. ).and_return(['file.txt'])
  987. flexmock(module.shutil).should_receive('rmtree')
  988. flexmock(module).should_receive('unmount_snapshot').with_args(
  989. 'umount',
  990. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  991. ).never()
  992. flexmock(module).should_receive('unmount_snapshot').with_args(
  993. 'umount',
  994. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  995. ).once()
  996. flexmock(module).should_receive('get_snapshots').and_return(
  997. (
  998. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  999. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  1000. ),
  1001. )
  1002. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  1003. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  1004. module.remove_data_source_dumps(
  1005. hook_config=config['lvm'],
  1006. config=config,
  1007. borgmatic_runtime_directory='/run/borgmatic',
  1008. dry_run=False,
  1009. )
  1010. def test_remove_data_source_dumps_with_successful_mount_point_removal_skips_unmount():
  1011. config = {'lvm': {}}
  1012. flexmock(module).should_receive('get_logical_volumes').and_return(
  1013. (
  1014. module.Logical_volume(
  1015. name='lvolume1',
  1016. device_path='/dev/lvolume1',
  1017. mount_point='/mnt/lvolume1',
  1018. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1019. ),
  1020. module.Logical_volume(
  1021. name='lvolume2',
  1022. device_path='/dev/lvolume2',
  1023. mount_point='/mnt/lvolume2',
  1024. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1025. ),
  1026. )
  1027. )
  1028. flexmock(module.borgmatic.config.paths).should_receive(
  1029. 'replace_temporary_subdirectory_with_glob'
  1030. ).and_return('/run/borgmatic')
  1031. flexmock(module.glob).should_receive('glob').replace_with(
  1032. lambda path: [path.replace('*', 'b33f')]
  1033. )
  1034. flexmock(module.os.path).should_receive('isdir').with_args(
  1035. '/run/borgmatic/lvm_snapshots/b33f'
  1036. ).and_return(True)
  1037. flexmock(module.os.path).should_receive('isdir').with_args(
  1038. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  1039. ).and_return(True).and_return(False)
  1040. flexmock(module.os.path).should_receive('isdir').with_args(
  1041. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  1042. ).and_return(True).and_return(True)
  1043. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1044. flexmock(module.shutil).should_receive('rmtree')
  1045. flexmock(module).should_receive('unmount_snapshot').with_args(
  1046. 'umount',
  1047. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1048. ).never()
  1049. flexmock(module).should_receive('unmount_snapshot').with_args(
  1050. 'umount',
  1051. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1052. ).once()
  1053. flexmock(module).should_receive('get_snapshots').and_return(
  1054. (
  1055. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  1056. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  1057. ),
  1058. )
  1059. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  1060. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  1061. module.remove_data_source_dumps(
  1062. hook_config=config['lvm'],
  1063. config=config,
  1064. borgmatic_runtime_directory='/run/borgmatic',
  1065. dry_run=False,
  1066. )
  1067. def test_remove_data_source_dumps_bails_for_missing_umount_command():
  1068. config = {'lvm': {}}
  1069. flexmock(module).should_receive('get_logical_volumes').and_return(
  1070. (
  1071. module.Logical_volume(
  1072. name='lvolume1',
  1073. device_path='/dev/lvolume1',
  1074. mount_point='/mnt/lvolume1',
  1075. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1076. ),
  1077. module.Logical_volume(
  1078. name='lvolume2',
  1079. device_path='/dev/lvolume2',
  1080. mount_point='/mnt/lvolume2',
  1081. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1082. ),
  1083. )
  1084. )
  1085. flexmock(module.borgmatic.config.paths).should_receive(
  1086. 'replace_temporary_subdirectory_with_glob'
  1087. ).and_return('/run/borgmatic')
  1088. flexmock(module.glob).should_receive('glob').replace_with(
  1089. lambda path: [path.replace('*', 'b33f')]
  1090. )
  1091. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1092. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1093. flexmock(module.shutil).should_receive('rmtree')
  1094. flexmock(module).should_receive('unmount_snapshot').with_args(
  1095. 'umount',
  1096. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1097. ).and_raise(FileNotFoundError)
  1098. flexmock(module).should_receive('unmount_snapshot').with_args(
  1099. 'umount',
  1100. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1101. ).never()
  1102. flexmock(module).should_receive('get_snapshots').never()
  1103. flexmock(module).should_receive('remove_snapshot').never()
  1104. module.remove_data_source_dumps(
  1105. hook_config=config['lvm'],
  1106. config=config,
  1107. borgmatic_runtime_directory='/run/borgmatic',
  1108. dry_run=False,
  1109. )
  1110. def test_remove_data_source_dumps_swallows_umount_command_error():
  1111. config = {'lvm': {}}
  1112. flexmock(module).should_receive('get_logical_volumes').and_return(
  1113. (
  1114. module.Logical_volume(
  1115. name='lvolume1',
  1116. device_path='/dev/lvolume1',
  1117. mount_point='/mnt/lvolume1',
  1118. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1119. ),
  1120. module.Logical_volume(
  1121. name='lvolume2',
  1122. device_path='/dev/lvolume2',
  1123. mount_point='/mnt/lvolume2',
  1124. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1125. ),
  1126. )
  1127. )
  1128. flexmock(module.borgmatic.config.paths).should_receive(
  1129. 'replace_temporary_subdirectory_with_glob'
  1130. ).and_return('/run/borgmatic')
  1131. flexmock(module.glob).should_receive('glob').replace_with(
  1132. lambda path: [path.replace('*', 'b33f')]
  1133. )
  1134. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1135. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1136. flexmock(module.shutil).should_receive('rmtree')
  1137. flexmock(module).should_receive('unmount_snapshot').with_args(
  1138. 'umount',
  1139. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1140. ).and_raise(module.subprocess.CalledProcessError(1, 'wtf'))
  1141. flexmock(module).should_receive('unmount_snapshot').with_args(
  1142. 'umount',
  1143. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1144. ).once()
  1145. flexmock(module).should_receive('get_snapshots').and_return(
  1146. (
  1147. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  1148. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  1149. ),
  1150. )
  1151. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  1152. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  1153. module.remove_data_source_dumps(
  1154. hook_config=config['lvm'],
  1155. config=config,
  1156. borgmatic_runtime_directory='/run/borgmatic',
  1157. dry_run=False,
  1158. )
  1159. def test_remove_data_source_dumps_bails_for_missing_lvs_command():
  1160. config = {'lvm': {}}
  1161. flexmock(module).should_receive('get_logical_volumes').and_return(
  1162. (
  1163. module.Logical_volume(
  1164. name='lvolume1',
  1165. device_path='/dev/lvolume1',
  1166. mount_point='/mnt/lvolume1',
  1167. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1168. ),
  1169. module.Logical_volume(
  1170. name='lvolume2',
  1171. device_path='/dev/lvolume2',
  1172. mount_point='/mnt/lvolume2',
  1173. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1174. ),
  1175. )
  1176. )
  1177. flexmock(module.borgmatic.config.paths).should_receive(
  1178. 'replace_temporary_subdirectory_with_glob'
  1179. ).and_return('/run/borgmatic')
  1180. flexmock(module.glob).should_receive('glob').replace_with(
  1181. lambda path: [path.replace('*', 'b33f')]
  1182. )
  1183. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1184. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1185. flexmock(module.shutil).should_receive('rmtree')
  1186. flexmock(module).should_receive('unmount_snapshot').with_args(
  1187. 'umount',
  1188. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1189. ).once()
  1190. flexmock(module).should_receive('unmount_snapshot').with_args(
  1191. 'umount',
  1192. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1193. ).once()
  1194. flexmock(module).should_receive('get_snapshots').and_raise(FileNotFoundError)
  1195. flexmock(module).should_receive('remove_snapshot').never()
  1196. module.remove_data_source_dumps(
  1197. hook_config=config['lvm'],
  1198. config=config,
  1199. borgmatic_runtime_directory='/run/borgmatic',
  1200. dry_run=False,
  1201. )
  1202. def test_remove_data_source_dumps_bails_for_lvs_command_error():
  1203. config = {'lvm': {}}
  1204. flexmock(module).should_receive('get_logical_volumes').and_return(
  1205. (
  1206. module.Logical_volume(
  1207. name='lvolume1',
  1208. device_path='/dev/lvolume1',
  1209. mount_point='/mnt/lvolume1',
  1210. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1211. ),
  1212. module.Logical_volume(
  1213. name='lvolume2',
  1214. device_path='/dev/lvolume2',
  1215. mount_point='/mnt/lvolume2',
  1216. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1217. ),
  1218. )
  1219. )
  1220. flexmock(module.borgmatic.config.paths).should_receive(
  1221. 'replace_temporary_subdirectory_with_glob'
  1222. ).and_return('/run/borgmatic')
  1223. flexmock(module.glob).should_receive('glob').replace_with(
  1224. lambda path: [path.replace('*', 'b33f')]
  1225. )
  1226. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1227. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1228. flexmock(module.shutil).should_receive('rmtree')
  1229. flexmock(module).should_receive('unmount_snapshot').with_args(
  1230. 'umount',
  1231. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1232. ).once()
  1233. flexmock(module).should_receive('unmount_snapshot').with_args(
  1234. 'umount',
  1235. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1236. ).once()
  1237. flexmock(module).should_receive('get_snapshots').and_raise(
  1238. module.subprocess.CalledProcessError(1, 'wtf')
  1239. )
  1240. flexmock(module).should_receive('remove_snapshot').never()
  1241. module.remove_data_source_dumps(
  1242. hook_config=config['lvm'],
  1243. config=config,
  1244. borgmatic_runtime_directory='/run/borgmatic',
  1245. dry_run=False,
  1246. )
  1247. def test_remove_data_source_with_dry_run_skips_snapshot_unmount_and_delete():
  1248. config = {'lvm': {}}
  1249. flexmock(module).should_receive('get_logical_volumes').and_return(
  1250. (
  1251. module.Logical_volume(
  1252. name='lvolume1',
  1253. device_path='/dev/lvolume1',
  1254. mount_point='/mnt/lvolume1',
  1255. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1256. ),
  1257. module.Logical_volume(
  1258. name='lvolume2',
  1259. device_path='/dev/lvolume2',
  1260. mount_point='/mnt/lvolume2',
  1261. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1262. ),
  1263. )
  1264. )
  1265. flexmock(module.borgmatic.config.paths).should_receive(
  1266. 'replace_temporary_subdirectory_with_glob'
  1267. ).and_return('/run/borgmatic')
  1268. flexmock(module.glob).should_receive('glob').replace_with(lambda path: [path])
  1269. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1270. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1271. flexmock(module.shutil).should_receive('rmtree').never()
  1272. flexmock(module).should_receive('unmount_snapshot').never()
  1273. flexmock(module).should_receive('get_snapshots').and_return(
  1274. (
  1275. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  1276. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  1277. module.Snapshot('nonborgmatic', '/dev/nonborgmatic'),
  1278. ),
  1279. ).once()
  1280. flexmock(module).should_receive('remove_snapshot').never()
  1281. module.remove_data_source_dumps(
  1282. hook_config=config['lvm'],
  1283. config=config,
  1284. borgmatic_runtime_directory='/run/borgmatic',
  1285. dry_run=True,
  1286. )