test_lvm.py 50 KB

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