test_lvm.py 51 KB

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