test_lvm.py 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373
  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. config = {'lvm': {}}
  257. patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  258. logical_volumes = (
  259. module.Logical_volume(
  260. name='lvolume1',
  261. device_path='/dev/lvolume1',
  262. mount_point='/mnt/lvolume1',
  263. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  264. ),
  265. module.Logical_volume(
  266. name='lvolume2',
  267. device_path='/dev/lvolume2',
  268. mount_point='/mnt/lvolume2',
  269. contained_patterns=(Pattern('/mnt/lvolume2'),),
  270. ),
  271. )
  272. flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes)
  273. flexmock(module.os).should_receive('getpid').and_return(1234)
  274. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  275. 'lvcreate', 'lvolume1_borgmatic-1234', '/dev/lvolume1', module.DEFAULT_SNAPSHOT_SIZE
  276. ).once()
  277. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  278. 'lvcreate', 'lvolume2_borgmatic-1234', '/dev/lvolume2', module.DEFAULT_SNAPSHOT_SIZE
  279. ).once()
  280. flexmock(module).should_receive('get_snapshots').with_args(
  281. 'lvs', snapshot_name='lvolume1_borgmatic-1234'
  282. ).and_return(
  283. (module.Snapshot(name='lvolume1_borgmatic-1234', device_path='/dev/lvolume1_snap'),)
  284. )
  285. flexmock(module).should_receive('get_snapshots').with_args(
  286. 'lvs', snapshot_name='lvolume2_borgmatic-1234'
  287. ).and_return(
  288. (module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
  289. )
  290. flexmock(module.hashlib).should_receive('shake_256').and_return(
  291. flexmock(hexdigest=lambda length: 'b33f')
  292. )
  293. flexmock(module).should_receive('mount_snapshot').with_args(
  294. 'mount', '/dev/lvolume1_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  295. ).once()
  296. flexmock(module).should_receive('mount_snapshot').with_args(
  297. 'mount', '/dev/lvolume2_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  298. ).once()
  299. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  300. Pattern('/mnt/lvolume1/subdir'), logical_volumes[0], '/run/borgmatic'
  301. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'))
  302. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  303. Pattern('/mnt/lvolume2'), logical_volumes[1], '/run/borgmatic'
  304. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
  305. assert (
  306. module.dump_data_sources(
  307. hook_config=config['lvm'],
  308. config=config,
  309. config_paths=('test.yaml',),
  310. borgmatic_runtime_directory='/run/borgmatic',
  311. patterns=patterns,
  312. dry_run=False,
  313. )
  314. == []
  315. )
  316. assert patterns == [
  317. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
  318. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
  319. ]
  320. def test_dump_data_sources_with_no_logical_volumes_skips_snapshots():
  321. config = {'lvm': {}}
  322. patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  323. flexmock(module).should_receive('get_logical_volumes').and_return(())
  324. flexmock(module).should_receive('snapshot_logical_volume').never()
  325. flexmock(module).should_receive('mount_snapshot').never()
  326. assert (
  327. module.dump_data_sources(
  328. hook_config=config['lvm'],
  329. config=config,
  330. config_paths=('test.yaml',),
  331. borgmatic_runtime_directory='/run/borgmatic',
  332. patterns=patterns,
  333. dry_run=False,
  334. )
  335. == []
  336. )
  337. assert patterns == [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  338. def test_dump_data_sources_uses_snapshot_size_for_snapshot():
  339. config = {'lvm': {'snapshot_size': '1000PB'}}
  340. patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  341. logical_volumes = (
  342. module.Logical_volume(
  343. name='lvolume1',
  344. device_path='/dev/lvolume1',
  345. mount_point='/mnt/lvolume1',
  346. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  347. ),
  348. module.Logical_volume(
  349. name='lvolume2',
  350. device_path='/dev/lvolume2',
  351. mount_point='/mnt/lvolume2',
  352. contained_patterns=(Pattern('/mnt/lvolume2'),),
  353. ),
  354. )
  355. flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes)
  356. flexmock(module.os).should_receive('getpid').and_return(1234)
  357. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  358. 'lvcreate',
  359. 'lvolume1_borgmatic-1234',
  360. '/dev/lvolume1',
  361. '1000PB',
  362. ).once()
  363. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  364. 'lvcreate',
  365. 'lvolume2_borgmatic-1234',
  366. '/dev/lvolume2',
  367. '1000PB',
  368. ).once()
  369. flexmock(module).should_receive('get_snapshots').with_args(
  370. 'lvs', snapshot_name='lvolume1_borgmatic-1234'
  371. ).and_return(
  372. (module.Snapshot(name='lvolume1_borgmatic-1234', device_path='/dev/lvolume1_snap'),)
  373. )
  374. flexmock(module).should_receive('get_snapshots').with_args(
  375. 'lvs', snapshot_name='lvolume2_borgmatic-1234'
  376. ).and_return(
  377. (module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
  378. )
  379. flexmock(module.hashlib).should_receive('shake_256').and_return(
  380. flexmock(hexdigest=lambda length: 'b33f')
  381. )
  382. flexmock(module).should_receive('mount_snapshot').with_args(
  383. 'mount', '/dev/lvolume1_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  384. ).once()
  385. flexmock(module).should_receive('mount_snapshot').with_args(
  386. 'mount', '/dev/lvolume2_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  387. ).once()
  388. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  389. Pattern('/mnt/lvolume1/subdir'), logical_volumes[0], '/run/borgmatic'
  390. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'))
  391. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  392. Pattern('/mnt/lvolume2'), logical_volumes[1], '/run/borgmatic'
  393. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
  394. assert (
  395. module.dump_data_sources(
  396. hook_config=config['lvm'],
  397. config=config,
  398. config_paths=('test.yaml',),
  399. borgmatic_runtime_directory='/run/borgmatic',
  400. patterns=patterns,
  401. dry_run=False,
  402. )
  403. == []
  404. )
  405. assert patterns == [
  406. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
  407. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
  408. ]
  409. def test_dump_data_sources_uses_custom_commands():
  410. config = {
  411. 'lvm': {
  412. 'lsblk_command': '/usr/local/bin/lsblk',
  413. 'lvcreate_command': '/usr/local/bin/lvcreate',
  414. 'lvs_command': '/usr/local/bin/lvs',
  415. 'mount_command': '/usr/local/bin/mount',
  416. },
  417. }
  418. patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  419. logical_volumes = (
  420. module.Logical_volume(
  421. name='lvolume1',
  422. device_path='/dev/lvolume1',
  423. mount_point='/mnt/lvolume1',
  424. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  425. ),
  426. module.Logical_volume(
  427. name='lvolume2',
  428. device_path='/dev/lvolume2',
  429. mount_point='/mnt/lvolume2',
  430. contained_patterns=(Pattern('/mnt/lvolume2'),),
  431. ),
  432. )
  433. flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes)
  434. flexmock(module.os).should_receive('getpid').and_return(1234)
  435. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  436. '/usr/local/bin/lvcreate',
  437. 'lvolume1_borgmatic-1234',
  438. '/dev/lvolume1',
  439. module.DEFAULT_SNAPSHOT_SIZE,
  440. ).once()
  441. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  442. '/usr/local/bin/lvcreate',
  443. 'lvolume2_borgmatic-1234',
  444. '/dev/lvolume2',
  445. module.DEFAULT_SNAPSHOT_SIZE,
  446. ).once()
  447. flexmock(module).should_receive('get_snapshots').with_args(
  448. '/usr/local/bin/lvs', snapshot_name='lvolume1_borgmatic-1234'
  449. ).and_return(
  450. (module.Snapshot(name='lvolume1_borgmatic-1234', device_path='/dev/lvolume1_snap'),)
  451. )
  452. flexmock(module).should_receive('get_snapshots').with_args(
  453. '/usr/local/bin/lvs', snapshot_name='lvolume2_borgmatic-1234'
  454. ).and_return(
  455. (module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
  456. )
  457. flexmock(module.hashlib).should_receive('shake_256').and_return(
  458. flexmock(hexdigest=lambda length: 'b33f')
  459. )
  460. flexmock(module).should_receive('mount_snapshot').with_args(
  461. '/usr/local/bin/mount',
  462. '/dev/lvolume1_snap',
  463. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  464. ).once()
  465. flexmock(module).should_receive('mount_snapshot').with_args(
  466. '/usr/local/bin/mount',
  467. '/dev/lvolume2_snap',
  468. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  469. ).once()
  470. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  471. Pattern('/mnt/lvolume1/subdir'), logical_volumes[0], '/run/borgmatic'
  472. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'))
  473. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  474. Pattern('/mnt/lvolume2'), logical_volumes[1], '/run/borgmatic'
  475. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
  476. assert (
  477. module.dump_data_sources(
  478. hook_config=config['lvm'],
  479. config=config,
  480. config_paths=('test.yaml',),
  481. borgmatic_runtime_directory='/run/borgmatic',
  482. patterns=patterns,
  483. dry_run=False,
  484. )
  485. == []
  486. )
  487. assert patterns == [
  488. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
  489. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
  490. ]
  491. def test_dump_data_sources_with_dry_run_skips_snapshots_and_does_not_touch_patterns():
  492. config = {'lvm': {}}
  493. patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  494. flexmock(module).should_receive('get_logical_volumes').and_return(
  495. (
  496. module.Logical_volume(
  497. name='lvolume1',
  498. device_path='/dev/lvolume1',
  499. mount_point='/mnt/lvolume1',
  500. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  501. ),
  502. module.Logical_volume(
  503. name='lvolume2',
  504. device_path='/dev/lvolume2',
  505. mount_point='/mnt/lvolume2',
  506. contained_patterns=(Pattern('/mnt/lvolume2'),),
  507. ),
  508. )
  509. )
  510. flexmock(module.os).should_receive('getpid').and_return(1234)
  511. flexmock(module).should_receive('snapshot_logical_volume').never()
  512. flexmock(module).should_receive('get_snapshots').with_args(
  513. 'lvs', snapshot_name='lvolume1_borgmatic-1234'
  514. ).and_return(
  515. (module.Snapshot(name='lvolume1_borgmatic-1234', device_path='/dev/lvolume1_snap'),)
  516. )
  517. flexmock(module).should_receive('get_snapshots').with_args(
  518. 'lvs', snapshot_name='lvolume2_borgmatic-1234'
  519. ).and_return(
  520. (module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
  521. )
  522. flexmock(module).should_receive('mount_snapshot').never()
  523. assert (
  524. module.dump_data_sources(
  525. hook_config=config['lvm'],
  526. config=config,
  527. config_paths=('test.yaml',),
  528. borgmatic_runtime_directory='/run/borgmatic',
  529. patterns=patterns,
  530. dry_run=True,
  531. )
  532. == []
  533. )
  534. assert patterns == [
  535. Pattern('/mnt/lvolume1/subdir'),
  536. Pattern('/mnt/lvolume2'),
  537. ]
  538. def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained_patterns():
  539. config = {'lvm': {}}
  540. patterns = [Pattern('/hmm')]
  541. logical_volumes = (
  542. module.Logical_volume(
  543. name='lvolume1',
  544. device_path='/dev/lvolume1',
  545. mount_point='/mnt/lvolume1',
  546. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  547. ),
  548. module.Logical_volume(
  549. name='lvolume2',
  550. device_path='/dev/lvolume2',
  551. mount_point='/mnt/lvolume2',
  552. contained_patterns=(Pattern('/mnt/lvolume2'),),
  553. ),
  554. )
  555. flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes)
  556. flexmock(module.os).should_receive('getpid').and_return(1234)
  557. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  558. 'lvcreate', 'lvolume1_borgmatic-1234', '/dev/lvolume1', module.DEFAULT_SNAPSHOT_SIZE
  559. ).once()
  560. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  561. 'lvcreate', 'lvolume2_borgmatic-1234', '/dev/lvolume2', module.DEFAULT_SNAPSHOT_SIZE
  562. ).once()
  563. flexmock(module).should_receive('get_snapshots').with_args(
  564. 'lvs', snapshot_name='lvolume1_borgmatic-1234'
  565. ).and_return(
  566. (module.Snapshot(name='lvolume1_borgmatic-1234', device_path='/dev/lvolume1_snap'),)
  567. )
  568. flexmock(module).should_receive('get_snapshots').with_args(
  569. 'lvs', snapshot_name='lvolume2_borgmatic-1234'
  570. ).and_return(
  571. (module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
  572. )
  573. flexmock(module.hashlib).should_receive('shake_256').and_return(
  574. flexmock(hexdigest=lambda length: 'b33f')
  575. )
  576. flexmock(module).should_receive('mount_snapshot').with_args(
  577. 'mount', '/dev/lvolume1_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  578. ).once()
  579. flexmock(module).should_receive('mount_snapshot').with_args(
  580. 'mount', '/dev/lvolume2_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  581. ).once()
  582. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  583. Pattern('/mnt/lvolume1/subdir'), logical_volumes[0], '/run/borgmatic'
  584. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'))
  585. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  586. Pattern('/mnt/lvolume2'), logical_volumes[1], '/run/borgmatic'
  587. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
  588. assert (
  589. module.dump_data_sources(
  590. hook_config=config['lvm'],
  591. config=config,
  592. config_paths=('test.yaml',),
  593. borgmatic_runtime_directory='/run/borgmatic',
  594. patterns=patterns,
  595. dry_run=False,
  596. )
  597. == []
  598. )
  599. assert patterns == [
  600. Pattern('/hmm'),
  601. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
  602. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
  603. ]
  604. def test_dump_data_sources_with_missing_snapshot_errors():
  605. config = {'lvm': {}}
  606. patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  607. flexmock(module).should_receive('get_logical_volumes').and_return(
  608. (
  609. module.Logical_volume(
  610. name='lvolume1',
  611. device_path='/dev/lvolume1',
  612. mount_point='/mnt/lvolume1',
  613. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  614. ),
  615. module.Logical_volume(
  616. name='lvolume2',
  617. device_path='/dev/lvolume2',
  618. mount_point='/mnt/lvolume2',
  619. contained_patterns=(Pattern('/mnt/lvolume2'),),
  620. ),
  621. )
  622. )
  623. flexmock(module.os).should_receive('getpid').and_return(1234)
  624. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  625. 'lvcreate', 'lvolume1_borgmatic-1234', '/dev/lvolume1', module.DEFAULT_SNAPSHOT_SIZE
  626. ).once()
  627. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  628. 'lvcreate', 'lvolume2_borgmatic-1234', '/dev/lvolume2', module.DEFAULT_SNAPSHOT_SIZE
  629. ).never()
  630. flexmock(module).should_receive('get_snapshots').with_args(
  631. 'lvs', snapshot_name='lvolume1_borgmatic-1234'
  632. ).and_return(())
  633. flexmock(module).should_receive('get_snapshots').with_args(
  634. 'lvs', snapshot_name='lvolume2_borgmatic-1234'
  635. ).never()
  636. flexmock(module).should_receive('mount_snapshot').never()
  637. with pytest.raises(ValueError):
  638. module.dump_data_sources(
  639. hook_config=config['lvm'],
  640. config=config,
  641. config_paths=('test.yaml',),
  642. borgmatic_runtime_directory='/run/borgmatic',
  643. patterns=patterns,
  644. dry_run=False,
  645. )
  646. def test_get_snapshots_lists_all_snapshots():
  647. flexmock(module.borgmatic.execute).should_receive(
  648. 'execute_command_and_capture_output'
  649. ).and_return(
  650. '''
  651. {
  652. "report": [
  653. {
  654. "lv": [
  655. {"lv_name": "snap1", "lv_path": "/dev/snap1"},
  656. {"lv_name": "snap2", "lv_path": "/dev/snap2"}
  657. ]
  658. }
  659. ],
  660. "log": [
  661. ]
  662. }
  663. '''
  664. )
  665. assert module.get_snapshots('lvs') == (
  666. module.Snapshot('snap1', '/dev/snap1'),
  667. module.Snapshot('snap2', '/dev/snap2'),
  668. )
  669. def test_get_snapshots_with_snapshot_name_lists_just_that_snapshot():
  670. flexmock(module.borgmatic.execute).should_receive(
  671. 'execute_command_and_capture_output'
  672. ).and_return(
  673. '''
  674. {
  675. "report": [
  676. {
  677. "lv": [
  678. {"lv_name": "snap1", "lv_path": "/dev/snap1"},
  679. {"lv_name": "snap2", "lv_path": "/dev/snap2"}
  680. ]
  681. }
  682. ],
  683. "log": [
  684. ]
  685. }
  686. '''
  687. )
  688. assert module.get_snapshots('lvs', snapshot_name='snap2') == (
  689. module.Snapshot('snap2', '/dev/snap2'),
  690. )
  691. def test_get_snapshots_with_invalid_lvs_json_errors():
  692. flexmock(module.borgmatic.execute).should_receive(
  693. 'execute_command_and_capture_output'
  694. ).and_return('{')
  695. with pytest.raises(ValueError):
  696. assert module.get_snapshots('lvs')
  697. def test_get_snapshots_with_lvs_json_missing_report_errors():
  698. flexmock(module.borgmatic.execute).should_receive(
  699. 'execute_command_and_capture_output'
  700. ).and_return(
  701. '''
  702. {
  703. "report": [],
  704. "log": [
  705. ]
  706. }
  707. '''
  708. )
  709. with pytest.raises(ValueError):
  710. assert module.get_snapshots('lvs')
  711. def test_get_snapshots_with_lvs_json_missing_keys_errors():
  712. flexmock(module.borgmatic.execute).should_receive(
  713. 'execute_command_and_capture_output'
  714. ).and_return(
  715. '''
  716. {
  717. "report": [
  718. {
  719. "lv": [
  720. {}
  721. ]
  722. }
  723. ],
  724. "log": [
  725. ]
  726. }
  727. '''
  728. )
  729. with pytest.raises(ValueError):
  730. assert module.get_snapshots('lvs')
  731. def test_remove_data_source_dumps_unmounts_and_remove_snapshots():
  732. config = {'lvm': {}}
  733. flexmock(module).should_receive('get_logical_volumes').and_return(
  734. (
  735. module.Logical_volume(
  736. name='lvolume1',
  737. device_path='/dev/lvolume1',
  738. mount_point='/mnt/lvolume1',
  739. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  740. ),
  741. module.Logical_volume(
  742. name='lvolume2',
  743. device_path='/dev/lvolume2',
  744. mount_point='/mnt/lvolume2',
  745. contained_patterns=(Pattern('/mnt/lvolume2'),),
  746. ),
  747. )
  748. )
  749. flexmock(module.borgmatic.config.paths).should_receive(
  750. 'replace_temporary_subdirectory_with_glob'
  751. ).and_return('/run/borgmatic')
  752. flexmock(module.glob).should_receive('glob').replace_with(
  753. lambda path: [path.replace('*', 'b33f')]
  754. )
  755. flexmock(module.os.path).should_receive('isdir').and_return(True)
  756. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  757. flexmock(module.shutil).should_receive('rmtree')
  758. flexmock(module).should_receive('unmount_snapshot').with_args(
  759. 'umount',
  760. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  761. ).once()
  762. flexmock(module).should_receive('unmount_snapshot').with_args(
  763. 'umount',
  764. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  765. ).once()
  766. flexmock(module).should_receive('get_snapshots').and_return(
  767. (
  768. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  769. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  770. module.Snapshot('nonborgmatic', '/dev/nonborgmatic'),
  771. ),
  772. )
  773. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  774. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  775. flexmock(module).should_receive('remove_snapshot').with_args(
  776. 'nonborgmatic', '/dev/nonborgmatic'
  777. ).never()
  778. module.remove_data_source_dumps(
  779. hook_config=config['lvm'],
  780. config=config,
  781. borgmatic_runtime_directory='/run/borgmatic',
  782. dry_run=False,
  783. )
  784. def test_remove_data_source_dumps_bails_for_missing_lvm_configuration():
  785. flexmock(module).should_receive('get_logical_volumes').never()
  786. flexmock(module.borgmatic.config.paths).should_receive(
  787. 'replace_temporary_subdirectory_with_glob'
  788. ).never()
  789. flexmock(module).should_receive('unmount_snapshot').never()
  790. flexmock(module).should_receive('remove_snapshot').never()
  791. module.remove_data_source_dumps(
  792. hook_config=None,
  793. config={'source_directories': '/mnt/lvolume'},
  794. borgmatic_runtime_directory='/run/borgmatic',
  795. dry_run=False,
  796. )
  797. def test_remove_data_source_dumps_bails_for_missing_lsblk_command():
  798. config = {'lvm': {}}
  799. flexmock(module).should_receive('get_logical_volumes').and_raise(FileNotFoundError)
  800. flexmock(module.borgmatic.config.paths).should_receive(
  801. 'replace_temporary_subdirectory_with_glob'
  802. ).never()
  803. flexmock(module).should_receive('unmount_snapshot').never()
  804. flexmock(module).should_receive('remove_snapshot').never()
  805. module.remove_data_source_dumps(
  806. hook_config=config['lvm'],
  807. config=config,
  808. borgmatic_runtime_directory='/run/borgmatic',
  809. dry_run=False,
  810. )
  811. def test_remove_data_source_dumps_bails_for_lsblk_command_error():
  812. config = {'lvm': {}}
  813. flexmock(module).should_receive('get_logical_volumes').and_raise(
  814. module.subprocess.CalledProcessError(1, 'wtf')
  815. )
  816. flexmock(module.borgmatic.config.paths).should_receive(
  817. 'replace_temporary_subdirectory_with_glob'
  818. ).never()
  819. flexmock(module).should_receive('unmount_snapshot').never()
  820. flexmock(module).should_receive('remove_snapshot').never()
  821. module.remove_data_source_dumps(
  822. hook_config=config['lvm'],
  823. config=config,
  824. borgmatic_runtime_directory='/run/borgmatic',
  825. dry_run=False,
  826. )
  827. def test_remove_data_source_dumps_with_missing_snapshot_directory_skips_unmount():
  828. config = {'lvm': {}}
  829. flexmock(module).should_receive('get_logical_volumes').and_return(
  830. (
  831. module.Logical_volume(
  832. name='lvolume1',
  833. device_path='/dev/lvolume1',
  834. mount_point='/mnt/lvolume1',
  835. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  836. ),
  837. module.Logical_volume(
  838. name='lvolume2',
  839. device_path='/dev/lvolume2',
  840. mount_point='/mnt/lvolume2',
  841. contained_patterns=(Pattern('/mnt/lvolume2'),),
  842. ),
  843. )
  844. )
  845. flexmock(module.borgmatic.config.paths).should_receive(
  846. 'replace_temporary_subdirectory_with_glob'
  847. ).and_return('/run/borgmatic')
  848. flexmock(module.glob).should_receive('glob').replace_with(
  849. lambda path: [path.replace('*', 'b33f')]
  850. )
  851. flexmock(module.os.path).should_receive('isdir').with_args(
  852. '/run/borgmatic/lvm_snapshots/b33f'
  853. ).and_return(False)
  854. flexmock(module.shutil).should_receive('rmtree').never()
  855. flexmock(module).should_receive('unmount_snapshot').never()
  856. flexmock(module).should_receive('get_snapshots').and_return(
  857. (
  858. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  859. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  860. ),
  861. )
  862. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  863. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  864. module.remove_data_source_dumps(
  865. hook_config=config['lvm'],
  866. config=config,
  867. borgmatic_runtime_directory='/run/borgmatic',
  868. dry_run=False,
  869. )
  870. def test_remove_data_source_dumps_with_missing_snapshot_mount_path_skips_unmount():
  871. config = {'lvm': {}}
  872. flexmock(module).should_receive('get_logical_volumes').and_return(
  873. (
  874. module.Logical_volume(
  875. name='lvolume1',
  876. device_path='/dev/lvolume1',
  877. mount_point='/mnt/lvolume1',
  878. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  879. ),
  880. module.Logical_volume(
  881. name='lvolume2',
  882. device_path='/dev/lvolume2',
  883. mount_point='/mnt/lvolume2',
  884. contained_patterns=(Pattern('/mnt/lvolume2'),),
  885. ),
  886. )
  887. )
  888. flexmock(module.borgmatic.config.paths).should_receive(
  889. 'replace_temporary_subdirectory_with_glob'
  890. ).and_return('/run/borgmatic')
  891. flexmock(module.glob).should_receive('glob').replace_with(
  892. lambda path: [path.replace('*', 'b33f')]
  893. )
  894. flexmock(module.os.path).should_receive('isdir').with_args(
  895. '/run/borgmatic/lvm_snapshots/b33f'
  896. ).and_return(True)
  897. flexmock(module.os.path).should_receive('isdir').with_args(
  898. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  899. ).and_return(False)
  900. flexmock(module.os.path).should_receive('isdir').with_args(
  901. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  902. ).and_return(True)
  903. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  904. flexmock(module.shutil).should_receive('rmtree')
  905. flexmock(module).should_receive('unmount_snapshot').with_args(
  906. 'umount',
  907. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  908. ).never()
  909. flexmock(module).should_receive('unmount_snapshot').with_args(
  910. 'umount',
  911. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  912. ).once()
  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_empty_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(True)
  957. flexmock(module.os).should_receive('listdir').with_args(
  958. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  959. ).and_return([])
  960. flexmock(module.os.path).should_receive('isdir').with_args(
  961. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  962. ).and_return(True)
  963. flexmock(module.os).should_receive('listdir').with_args(
  964. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  965. ).and_return(['file.txt'])
  966. flexmock(module.shutil).should_receive('rmtree')
  967. flexmock(module).should_receive('unmount_snapshot').with_args(
  968. 'umount',
  969. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  970. ).never()
  971. flexmock(module).should_receive('unmount_snapshot').with_args(
  972. 'umount',
  973. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  974. ).once()
  975. flexmock(module).should_receive('get_snapshots').and_return(
  976. (
  977. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  978. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  979. ),
  980. )
  981. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  982. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  983. module.remove_data_source_dumps(
  984. hook_config=config['lvm'],
  985. config=config,
  986. borgmatic_runtime_directory='/run/borgmatic',
  987. dry_run=False,
  988. )
  989. def test_remove_data_source_dumps_with_successful_mount_point_removal_skips_unmount():
  990. config = {'lvm': {}}
  991. flexmock(module).should_receive('get_logical_volumes').and_return(
  992. (
  993. module.Logical_volume(
  994. name='lvolume1',
  995. device_path='/dev/lvolume1',
  996. mount_point='/mnt/lvolume1',
  997. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  998. ),
  999. module.Logical_volume(
  1000. name='lvolume2',
  1001. device_path='/dev/lvolume2',
  1002. mount_point='/mnt/lvolume2',
  1003. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1004. ),
  1005. )
  1006. )
  1007. flexmock(module.borgmatic.config.paths).should_receive(
  1008. 'replace_temporary_subdirectory_with_glob'
  1009. ).and_return('/run/borgmatic')
  1010. flexmock(module.glob).should_receive('glob').replace_with(
  1011. lambda path: [path.replace('*', 'b33f')]
  1012. )
  1013. flexmock(module.os.path).should_receive('isdir').with_args(
  1014. '/run/borgmatic/lvm_snapshots/b33f'
  1015. ).and_return(True)
  1016. flexmock(module.os.path).should_receive('isdir').with_args(
  1017. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  1018. ).and_return(True).and_return(False)
  1019. flexmock(module.os.path).should_receive('isdir').with_args(
  1020. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  1021. ).and_return(True).and_return(True)
  1022. flexmock(module.os).should_receive('listdir').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_bails_for_missing_umount_command():
  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').and_return(True)
  1071. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1072. flexmock(module.shutil).should_receive('rmtree')
  1073. flexmock(module).should_receive('unmount_snapshot').with_args(
  1074. 'umount',
  1075. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1076. ).and_raise(FileNotFoundError)
  1077. flexmock(module).should_receive('unmount_snapshot').with_args(
  1078. 'umount',
  1079. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1080. ).never()
  1081. flexmock(module).should_receive('get_snapshots').never()
  1082. flexmock(module).should_receive('remove_snapshot').never()
  1083. module.remove_data_source_dumps(
  1084. hook_config=config['lvm'],
  1085. config=config,
  1086. borgmatic_runtime_directory='/run/borgmatic',
  1087. dry_run=False,
  1088. )
  1089. def test_remove_data_source_dumps_swallows_umount_command_error():
  1090. config = {'lvm': {}}
  1091. flexmock(module).should_receive('get_logical_volumes').and_return(
  1092. (
  1093. module.Logical_volume(
  1094. name='lvolume1',
  1095. device_path='/dev/lvolume1',
  1096. mount_point='/mnt/lvolume1',
  1097. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1098. ),
  1099. module.Logical_volume(
  1100. name='lvolume2',
  1101. device_path='/dev/lvolume2',
  1102. mount_point='/mnt/lvolume2',
  1103. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1104. ),
  1105. )
  1106. )
  1107. flexmock(module.borgmatic.config.paths).should_receive(
  1108. 'replace_temporary_subdirectory_with_glob'
  1109. ).and_return('/run/borgmatic')
  1110. flexmock(module.glob).should_receive('glob').replace_with(
  1111. lambda path: [path.replace('*', 'b33f')]
  1112. )
  1113. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1114. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1115. flexmock(module.shutil).should_receive('rmtree')
  1116. flexmock(module).should_receive('unmount_snapshot').with_args(
  1117. 'umount',
  1118. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1119. ).and_raise(module.subprocess.CalledProcessError(1, 'wtf'))
  1120. flexmock(module).should_receive('unmount_snapshot').with_args(
  1121. 'umount',
  1122. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1123. ).once()
  1124. flexmock(module).should_receive('get_snapshots').and_return(
  1125. (
  1126. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  1127. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  1128. ),
  1129. )
  1130. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  1131. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  1132. module.remove_data_source_dumps(
  1133. hook_config=config['lvm'],
  1134. config=config,
  1135. borgmatic_runtime_directory='/run/borgmatic',
  1136. dry_run=False,
  1137. )
  1138. def test_remove_data_source_dumps_bails_for_missing_lvs_command():
  1139. config = {'lvm': {}}
  1140. flexmock(module).should_receive('get_logical_volumes').and_return(
  1141. (
  1142. module.Logical_volume(
  1143. name='lvolume1',
  1144. device_path='/dev/lvolume1',
  1145. mount_point='/mnt/lvolume1',
  1146. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1147. ),
  1148. module.Logical_volume(
  1149. name='lvolume2',
  1150. device_path='/dev/lvolume2',
  1151. mount_point='/mnt/lvolume2',
  1152. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1153. ),
  1154. )
  1155. )
  1156. flexmock(module.borgmatic.config.paths).should_receive(
  1157. 'replace_temporary_subdirectory_with_glob'
  1158. ).and_return('/run/borgmatic')
  1159. flexmock(module.glob).should_receive('glob').replace_with(
  1160. lambda path: [path.replace('*', 'b33f')]
  1161. )
  1162. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1163. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1164. flexmock(module.shutil).should_receive('rmtree')
  1165. flexmock(module).should_receive('unmount_snapshot').with_args(
  1166. 'umount',
  1167. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1168. ).once()
  1169. flexmock(module).should_receive('unmount_snapshot').with_args(
  1170. 'umount',
  1171. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1172. ).once()
  1173. flexmock(module).should_receive('get_snapshots').and_raise(FileNotFoundError)
  1174. flexmock(module).should_receive('remove_snapshot').never()
  1175. module.remove_data_source_dumps(
  1176. hook_config=config['lvm'],
  1177. config=config,
  1178. borgmatic_runtime_directory='/run/borgmatic',
  1179. dry_run=False,
  1180. )
  1181. def test_remove_data_source_dumps_bails_for_lvs_command_error():
  1182. config = {'lvm': {}}
  1183. flexmock(module).should_receive('get_logical_volumes').and_return(
  1184. (
  1185. module.Logical_volume(
  1186. name='lvolume1',
  1187. device_path='/dev/lvolume1',
  1188. mount_point='/mnt/lvolume1',
  1189. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1190. ),
  1191. module.Logical_volume(
  1192. name='lvolume2',
  1193. device_path='/dev/lvolume2',
  1194. mount_point='/mnt/lvolume2',
  1195. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1196. ),
  1197. )
  1198. )
  1199. flexmock(module.borgmatic.config.paths).should_receive(
  1200. 'replace_temporary_subdirectory_with_glob'
  1201. ).and_return('/run/borgmatic')
  1202. flexmock(module.glob).should_receive('glob').replace_with(
  1203. lambda path: [path.replace('*', 'b33f')]
  1204. )
  1205. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1206. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1207. flexmock(module.shutil).should_receive('rmtree')
  1208. flexmock(module).should_receive('unmount_snapshot').with_args(
  1209. 'umount',
  1210. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1211. ).once()
  1212. flexmock(module).should_receive('unmount_snapshot').with_args(
  1213. 'umount',
  1214. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1215. ).once()
  1216. flexmock(module).should_receive('get_snapshots').and_raise(
  1217. module.subprocess.CalledProcessError(1, 'wtf')
  1218. )
  1219. flexmock(module).should_receive('remove_snapshot').never()
  1220. module.remove_data_source_dumps(
  1221. hook_config=config['lvm'],
  1222. config=config,
  1223. borgmatic_runtime_directory='/run/borgmatic',
  1224. dry_run=False,
  1225. )
  1226. def test_remove_data_source_with_dry_run_skips_snapshot_unmount_and_delete():
  1227. config = {'lvm': {}}
  1228. flexmock(module).should_receive('get_logical_volumes').and_return(
  1229. (
  1230. module.Logical_volume(
  1231. name='lvolume1',
  1232. device_path='/dev/lvolume1',
  1233. mount_point='/mnt/lvolume1',
  1234. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1235. ),
  1236. module.Logical_volume(
  1237. name='lvolume2',
  1238. device_path='/dev/lvolume2',
  1239. mount_point='/mnt/lvolume2',
  1240. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1241. ),
  1242. )
  1243. )
  1244. flexmock(module.borgmatic.config.paths).should_receive(
  1245. 'replace_temporary_subdirectory_with_glob'
  1246. ).and_return('/run/borgmatic')
  1247. flexmock(module.glob).should_receive('glob').replace_with(lambda path: [path])
  1248. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1249. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1250. flexmock(module.shutil).should_receive('rmtree').never()
  1251. flexmock(module).should_receive('unmount_snapshot').never()
  1252. flexmock(module).should_receive('get_snapshots').and_return(
  1253. (
  1254. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  1255. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  1256. module.Snapshot('nonborgmatic', '/dev/nonborgmatic'),
  1257. ),
  1258. ).once()
  1259. flexmock(module).should_receive('remove_snapshot').never()
  1260. module.remove_data_source_dumps(
  1261. hook_config=config['lvm'],
  1262. config=config,
  1263. borgmatic_runtime_directory='/run/borgmatic',
  1264. dry_run=True,
  1265. )