test_lvm.py 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366
  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').never()
  515. flexmock(module).should_receive('mount_snapshot').never()
  516. assert (
  517. module.dump_data_sources(
  518. hook_config=config['lvm'],
  519. config=config,
  520. config_paths=('test.yaml',),
  521. borgmatic_runtime_directory='/run/borgmatic',
  522. patterns=patterns,
  523. dry_run=True,
  524. )
  525. == []
  526. )
  527. assert patterns == [
  528. Pattern('/mnt/lvolume1/subdir'),
  529. Pattern('/mnt/lvolume2'),
  530. ]
  531. def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained_patterns():
  532. config = {'lvm': {}}
  533. patterns = [Pattern('/hmm')]
  534. logical_volumes = (
  535. module.Logical_volume(
  536. name='lvolume1',
  537. device_path='/dev/lvolume1',
  538. mount_point='/mnt/lvolume1',
  539. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  540. ),
  541. module.Logical_volume(
  542. name='lvolume2',
  543. device_path='/dev/lvolume2',
  544. mount_point='/mnt/lvolume2',
  545. contained_patterns=(Pattern('/mnt/lvolume2'),),
  546. ),
  547. )
  548. flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes)
  549. flexmock(module.os).should_receive('getpid').and_return(1234)
  550. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  551. 'lvcreate', 'lvolume1_borgmatic-1234', '/dev/lvolume1', module.DEFAULT_SNAPSHOT_SIZE
  552. ).once()
  553. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  554. 'lvcreate', 'lvolume2_borgmatic-1234', '/dev/lvolume2', module.DEFAULT_SNAPSHOT_SIZE
  555. ).once()
  556. flexmock(module).should_receive('get_snapshots').with_args(
  557. 'lvs', snapshot_name='lvolume1_borgmatic-1234'
  558. ).and_return(
  559. (module.Snapshot(name='lvolume1_borgmatic-1234', device_path='/dev/lvolume1_snap'),)
  560. )
  561. flexmock(module).should_receive('get_snapshots').with_args(
  562. 'lvs', snapshot_name='lvolume2_borgmatic-1234'
  563. ).and_return(
  564. (module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),)
  565. )
  566. flexmock(module.hashlib).should_receive('shake_256').and_return(
  567. flexmock(hexdigest=lambda length: 'b33f')
  568. )
  569. flexmock(module).should_receive('mount_snapshot').with_args(
  570. 'mount', '/dev/lvolume1_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  571. ).once()
  572. flexmock(module).should_receive('mount_snapshot').with_args(
  573. 'mount', '/dev/lvolume2_snap', '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  574. ).once()
  575. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  576. Pattern('/mnt/lvolume1/subdir'), logical_volumes[0], '/run/borgmatic'
  577. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'))
  578. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  579. Pattern('/mnt/lvolume2'), logical_volumes[1], '/run/borgmatic'
  580. ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
  581. assert (
  582. module.dump_data_sources(
  583. hook_config=config['lvm'],
  584. config=config,
  585. config_paths=('test.yaml',),
  586. borgmatic_runtime_directory='/run/borgmatic',
  587. patterns=patterns,
  588. dry_run=False,
  589. )
  590. == []
  591. )
  592. assert patterns == [
  593. Pattern('/hmm'),
  594. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
  595. Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
  596. ]
  597. def test_dump_data_sources_with_missing_snapshot_errors():
  598. config = {'lvm': {}}
  599. patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
  600. flexmock(module).should_receive('get_logical_volumes').and_return(
  601. (
  602. module.Logical_volume(
  603. name='lvolume1',
  604. device_path='/dev/lvolume1',
  605. mount_point='/mnt/lvolume1',
  606. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  607. ),
  608. module.Logical_volume(
  609. name='lvolume2',
  610. device_path='/dev/lvolume2',
  611. mount_point='/mnt/lvolume2',
  612. contained_patterns=(Pattern('/mnt/lvolume2'),),
  613. ),
  614. )
  615. )
  616. flexmock(module.os).should_receive('getpid').and_return(1234)
  617. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  618. 'lvcreate', 'lvolume1_borgmatic-1234', '/dev/lvolume1', module.DEFAULT_SNAPSHOT_SIZE
  619. ).once()
  620. flexmock(module).should_receive('snapshot_logical_volume').with_args(
  621. 'lvcreate', 'lvolume2_borgmatic-1234', '/dev/lvolume2', module.DEFAULT_SNAPSHOT_SIZE
  622. ).never()
  623. flexmock(module).should_receive('get_snapshots').with_args(
  624. 'lvs', snapshot_name='lvolume1_borgmatic-1234'
  625. ).and_return(())
  626. flexmock(module).should_receive('get_snapshots').with_args(
  627. 'lvs', snapshot_name='lvolume2_borgmatic-1234'
  628. ).never()
  629. flexmock(module).should_receive('mount_snapshot').never()
  630. with pytest.raises(ValueError):
  631. module.dump_data_sources(
  632. hook_config=config['lvm'],
  633. config=config,
  634. config_paths=('test.yaml',),
  635. borgmatic_runtime_directory='/run/borgmatic',
  636. patterns=patterns,
  637. dry_run=False,
  638. )
  639. def test_get_snapshots_lists_all_snapshots():
  640. flexmock(module.borgmatic.execute).should_receive(
  641. 'execute_command_and_capture_output'
  642. ).and_return(
  643. '''
  644. {
  645. "report": [
  646. {
  647. "lv": [
  648. {"lv_name": "snap1", "lv_path": "/dev/snap1"},
  649. {"lv_name": "snap2", "lv_path": "/dev/snap2"}
  650. ]
  651. }
  652. ],
  653. "log": [
  654. ]
  655. }
  656. '''
  657. )
  658. assert module.get_snapshots('lvs') == (
  659. module.Snapshot('snap1', '/dev/snap1'),
  660. module.Snapshot('snap2', '/dev/snap2'),
  661. )
  662. def test_get_snapshots_with_snapshot_name_lists_just_that_snapshot():
  663. flexmock(module.borgmatic.execute).should_receive(
  664. 'execute_command_and_capture_output'
  665. ).and_return(
  666. '''
  667. {
  668. "report": [
  669. {
  670. "lv": [
  671. {"lv_name": "snap1", "lv_path": "/dev/snap1"},
  672. {"lv_name": "snap2", "lv_path": "/dev/snap2"}
  673. ]
  674. }
  675. ],
  676. "log": [
  677. ]
  678. }
  679. '''
  680. )
  681. assert module.get_snapshots('lvs', snapshot_name='snap2') == (
  682. module.Snapshot('snap2', '/dev/snap2'),
  683. )
  684. def test_get_snapshots_with_invalid_lvs_json_errors():
  685. flexmock(module.borgmatic.execute).should_receive(
  686. 'execute_command_and_capture_output'
  687. ).and_return('{')
  688. with pytest.raises(ValueError):
  689. assert module.get_snapshots('lvs')
  690. def test_get_snapshots_with_lvs_json_missing_report_errors():
  691. flexmock(module.borgmatic.execute).should_receive(
  692. 'execute_command_and_capture_output'
  693. ).and_return(
  694. '''
  695. {
  696. "report": [],
  697. "log": [
  698. ]
  699. }
  700. '''
  701. )
  702. with pytest.raises(ValueError):
  703. assert module.get_snapshots('lvs')
  704. def test_get_snapshots_with_lvs_json_missing_keys_errors():
  705. flexmock(module.borgmatic.execute).should_receive(
  706. 'execute_command_and_capture_output'
  707. ).and_return(
  708. '''
  709. {
  710. "report": [
  711. {
  712. "lv": [
  713. {}
  714. ]
  715. }
  716. ],
  717. "log": [
  718. ]
  719. }
  720. '''
  721. )
  722. with pytest.raises(ValueError):
  723. assert module.get_snapshots('lvs')
  724. def test_remove_data_source_dumps_unmounts_and_remove_snapshots():
  725. config = {'lvm': {}}
  726. flexmock(module).should_receive('get_logical_volumes').and_return(
  727. (
  728. module.Logical_volume(
  729. name='lvolume1',
  730. device_path='/dev/lvolume1',
  731. mount_point='/mnt/lvolume1',
  732. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  733. ),
  734. module.Logical_volume(
  735. name='lvolume2',
  736. device_path='/dev/lvolume2',
  737. mount_point='/mnt/lvolume2',
  738. contained_patterns=(Pattern('/mnt/lvolume2'),),
  739. ),
  740. )
  741. )
  742. flexmock(module.borgmatic.config.paths).should_receive(
  743. 'replace_temporary_subdirectory_with_glob'
  744. ).and_return('/run/borgmatic')
  745. flexmock(module.glob).should_receive('glob').replace_with(
  746. lambda path: [path.replace('*', 'b33f')]
  747. )
  748. flexmock(module.os.path).should_receive('isdir').and_return(True)
  749. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  750. flexmock(module.shutil).should_receive('rmtree')
  751. flexmock(module).should_receive('unmount_snapshot').with_args(
  752. 'umount',
  753. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  754. ).once()
  755. flexmock(module).should_receive('unmount_snapshot').with_args(
  756. 'umount',
  757. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  758. ).once()
  759. flexmock(module).should_receive('get_snapshots').and_return(
  760. (
  761. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  762. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  763. module.Snapshot('nonborgmatic', '/dev/nonborgmatic'),
  764. ),
  765. )
  766. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  767. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  768. flexmock(module).should_receive('remove_snapshot').with_args(
  769. 'nonborgmatic', '/dev/nonborgmatic'
  770. ).never()
  771. module.remove_data_source_dumps(
  772. hook_config=config['lvm'],
  773. config=config,
  774. borgmatic_runtime_directory='/run/borgmatic',
  775. dry_run=False,
  776. )
  777. def test_remove_data_source_dumps_bails_for_missing_lvm_configuration():
  778. flexmock(module).should_receive('get_logical_volumes').never()
  779. flexmock(module.borgmatic.config.paths).should_receive(
  780. 'replace_temporary_subdirectory_with_glob'
  781. ).never()
  782. flexmock(module).should_receive('unmount_snapshot').never()
  783. flexmock(module).should_receive('remove_snapshot').never()
  784. module.remove_data_source_dumps(
  785. hook_config=None,
  786. config={'source_directories': '/mnt/lvolume'},
  787. borgmatic_runtime_directory='/run/borgmatic',
  788. dry_run=False,
  789. )
  790. def test_remove_data_source_dumps_bails_for_missing_lsblk_command():
  791. config = {'lvm': {}}
  792. flexmock(module).should_receive('get_logical_volumes').and_raise(FileNotFoundError)
  793. flexmock(module.borgmatic.config.paths).should_receive(
  794. 'replace_temporary_subdirectory_with_glob'
  795. ).never()
  796. flexmock(module).should_receive('unmount_snapshot').never()
  797. flexmock(module).should_receive('remove_snapshot').never()
  798. module.remove_data_source_dumps(
  799. hook_config=config['lvm'],
  800. config=config,
  801. borgmatic_runtime_directory='/run/borgmatic',
  802. dry_run=False,
  803. )
  804. def test_remove_data_source_dumps_bails_for_lsblk_command_error():
  805. config = {'lvm': {}}
  806. flexmock(module).should_receive('get_logical_volumes').and_raise(
  807. module.subprocess.CalledProcessError(1, 'wtf')
  808. )
  809. flexmock(module.borgmatic.config.paths).should_receive(
  810. 'replace_temporary_subdirectory_with_glob'
  811. ).never()
  812. flexmock(module).should_receive('unmount_snapshot').never()
  813. flexmock(module).should_receive('remove_snapshot').never()
  814. module.remove_data_source_dumps(
  815. hook_config=config['lvm'],
  816. config=config,
  817. borgmatic_runtime_directory='/run/borgmatic',
  818. dry_run=False,
  819. )
  820. def test_remove_data_source_dumps_with_missing_snapshot_directory_skips_unmount():
  821. config = {'lvm': {}}
  822. flexmock(module).should_receive('get_logical_volumes').and_return(
  823. (
  824. module.Logical_volume(
  825. name='lvolume1',
  826. device_path='/dev/lvolume1',
  827. mount_point='/mnt/lvolume1',
  828. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  829. ),
  830. module.Logical_volume(
  831. name='lvolume2',
  832. device_path='/dev/lvolume2',
  833. mount_point='/mnt/lvolume2',
  834. contained_patterns=(Pattern('/mnt/lvolume2'),),
  835. ),
  836. )
  837. )
  838. flexmock(module.borgmatic.config.paths).should_receive(
  839. 'replace_temporary_subdirectory_with_glob'
  840. ).and_return('/run/borgmatic')
  841. flexmock(module.glob).should_receive('glob').replace_with(
  842. lambda path: [path.replace('*', 'b33f')]
  843. )
  844. flexmock(module.os.path).should_receive('isdir').with_args(
  845. '/run/borgmatic/lvm_snapshots/b33f'
  846. ).and_return(False)
  847. flexmock(module.shutil).should_receive('rmtree').never()
  848. flexmock(module).should_receive('unmount_snapshot').never()
  849. flexmock(module).should_receive('get_snapshots').and_return(
  850. (
  851. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  852. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  853. ),
  854. )
  855. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  856. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  857. module.remove_data_source_dumps(
  858. hook_config=config['lvm'],
  859. config=config,
  860. borgmatic_runtime_directory='/run/borgmatic',
  861. dry_run=False,
  862. )
  863. def test_remove_data_source_dumps_with_missing_snapshot_mount_path_skips_unmount():
  864. config = {'lvm': {}}
  865. flexmock(module).should_receive('get_logical_volumes').and_return(
  866. (
  867. module.Logical_volume(
  868. name='lvolume1',
  869. device_path='/dev/lvolume1',
  870. mount_point='/mnt/lvolume1',
  871. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  872. ),
  873. module.Logical_volume(
  874. name='lvolume2',
  875. device_path='/dev/lvolume2',
  876. mount_point='/mnt/lvolume2',
  877. contained_patterns=(Pattern('/mnt/lvolume2'),),
  878. ),
  879. )
  880. )
  881. flexmock(module.borgmatic.config.paths).should_receive(
  882. 'replace_temporary_subdirectory_with_glob'
  883. ).and_return('/run/borgmatic')
  884. flexmock(module.glob).should_receive('glob').replace_with(
  885. lambda path: [path.replace('*', 'b33f')]
  886. )
  887. flexmock(module.os.path).should_receive('isdir').with_args(
  888. '/run/borgmatic/lvm_snapshots/b33f'
  889. ).and_return(True)
  890. flexmock(module.os.path).should_receive('isdir').with_args(
  891. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  892. ).and_return(False)
  893. flexmock(module.os.path).should_receive('isdir').with_args(
  894. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  895. ).and_return(True)
  896. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  897. flexmock(module.shutil).should_receive('rmtree')
  898. flexmock(module).should_receive('unmount_snapshot').with_args(
  899. 'umount',
  900. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  901. ).never()
  902. flexmock(module).should_receive('unmount_snapshot').with_args(
  903. 'umount',
  904. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  905. ).once()
  906. flexmock(module).should_receive('get_snapshots').and_return(
  907. (
  908. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  909. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  910. ),
  911. )
  912. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  913. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  914. module.remove_data_source_dumps(
  915. hook_config=config['lvm'],
  916. config=config,
  917. borgmatic_runtime_directory='/run/borgmatic',
  918. dry_run=False,
  919. )
  920. def test_remove_data_source_dumps_with_empty_snapshot_mount_path_skips_unmount():
  921. config = {'lvm': {}}
  922. flexmock(module).should_receive('get_logical_volumes').and_return(
  923. (
  924. module.Logical_volume(
  925. name='lvolume1',
  926. device_path='/dev/lvolume1',
  927. mount_point='/mnt/lvolume1',
  928. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  929. ),
  930. module.Logical_volume(
  931. name='lvolume2',
  932. device_path='/dev/lvolume2',
  933. mount_point='/mnt/lvolume2',
  934. contained_patterns=(Pattern('/mnt/lvolume2'),),
  935. ),
  936. )
  937. )
  938. flexmock(module.borgmatic.config.paths).should_receive(
  939. 'replace_temporary_subdirectory_with_glob'
  940. ).and_return('/run/borgmatic')
  941. flexmock(module.glob).should_receive('glob').replace_with(
  942. lambda path: [path.replace('*', 'b33f')]
  943. )
  944. flexmock(module.os.path).should_receive('isdir').with_args(
  945. '/run/borgmatic/lvm_snapshots/b33f'
  946. ).and_return(True)
  947. flexmock(module.os.path).should_receive('isdir').with_args(
  948. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  949. ).and_return(True)
  950. flexmock(module.os).should_receive('listdir').with_args(
  951. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  952. ).and_return([])
  953. flexmock(module.os.path).should_receive('isdir').with_args(
  954. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  955. ).and_return(True)
  956. flexmock(module.os).should_receive('listdir').with_args(
  957. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  958. ).and_return(['file.txt'])
  959. flexmock(module.shutil).should_receive('rmtree')
  960. flexmock(module).should_receive('unmount_snapshot').with_args(
  961. 'umount',
  962. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  963. ).never()
  964. flexmock(module).should_receive('unmount_snapshot').with_args(
  965. 'umount',
  966. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  967. ).once()
  968. flexmock(module).should_receive('get_snapshots').and_return(
  969. (
  970. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  971. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  972. ),
  973. )
  974. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  975. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  976. module.remove_data_source_dumps(
  977. hook_config=config['lvm'],
  978. config=config,
  979. borgmatic_runtime_directory='/run/borgmatic',
  980. dry_run=False,
  981. )
  982. def test_remove_data_source_dumps_with_successful_mount_point_removal_skips_unmount():
  983. config = {'lvm': {}}
  984. flexmock(module).should_receive('get_logical_volumes').and_return(
  985. (
  986. module.Logical_volume(
  987. name='lvolume1',
  988. device_path='/dev/lvolume1',
  989. mount_point='/mnt/lvolume1',
  990. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  991. ),
  992. module.Logical_volume(
  993. name='lvolume2',
  994. device_path='/dev/lvolume2',
  995. mount_point='/mnt/lvolume2',
  996. contained_patterns=(Pattern('/mnt/lvolume2'),),
  997. ),
  998. )
  999. )
  1000. flexmock(module.borgmatic.config.paths).should_receive(
  1001. 'replace_temporary_subdirectory_with_glob'
  1002. ).and_return('/run/borgmatic')
  1003. flexmock(module.glob).should_receive('glob').replace_with(
  1004. lambda path: [path.replace('*', 'b33f')]
  1005. )
  1006. flexmock(module.os.path).should_receive('isdir').with_args(
  1007. '/run/borgmatic/lvm_snapshots/b33f'
  1008. ).and_return(True)
  1009. flexmock(module.os.path).should_receive('isdir').with_args(
  1010. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1'
  1011. ).and_return(True).and_return(False)
  1012. flexmock(module.os.path).should_receive('isdir').with_args(
  1013. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2'
  1014. ).and_return(True).and_return(True)
  1015. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1016. flexmock(module.shutil).should_receive('rmtree')
  1017. flexmock(module).should_receive('unmount_snapshot').with_args(
  1018. 'umount',
  1019. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1020. ).never()
  1021. flexmock(module).should_receive('unmount_snapshot').with_args(
  1022. 'umount',
  1023. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1024. ).once()
  1025. flexmock(module).should_receive('get_snapshots').and_return(
  1026. (
  1027. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  1028. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  1029. ),
  1030. )
  1031. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  1032. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  1033. module.remove_data_source_dumps(
  1034. hook_config=config['lvm'],
  1035. config=config,
  1036. borgmatic_runtime_directory='/run/borgmatic',
  1037. dry_run=False,
  1038. )
  1039. def test_remove_data_source_dumps_bails_for_missing_umount_command():
  1040. config = {'lvm': {}}
  1041. flexmock(module).should_receive('get_logical_volumes').and_return(
  1042. (
  1043. module.Logical_volume(
  1044. name='lvolume1',
  1045. device_path='/dev/lvolume1',
  1046. mount_point='/mnt/lvolume1',
  1047. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1048. ),
  1049. module.Logical_volume(
  1050. name='lvolume2',
  1051. device_path='/dev/lvolume2',
  1052. mount_point='/mnt/lvolume2',
  1053. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1054. ),
  1055. )
  1056. )
  1057. flexmock(module.borgmatic.config.paths).should_receive(
  1058. 'replace_temporary_subdirectory_with_glob'
  1059. ).and_return('/run/borgmatic')
  1060. flexmock(module.glob).should_receive('glob').replace_with(
  1061. lambda path: [path.replace('*', 'b33f')]
  1062. )
  1063. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1064. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1065. flexmock(module.shutil).should_receive('rmtree')
  1066. flexmock(module).should_receive('unmount_snapshot').with_args(
  1067. 'umount',
  1068. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1069. ).and_raise(FileNotFoundError)
  1070. flexmock(module).should_receive('unmount_snapshot').with_args(
  1071. 'umount',
  1072. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1073. ).never()
  1074. flexmock(module).should_receive('get_snapshots').never()
  1075. flexmock(module).should_receive('remove_snapshot').never()
  1076. module.remove_data_source_dumps(
  1077. hook_config=config['lvm'],
  1078. config=config,
  1079. borgmatic_runtime_directory='/run/borgmatic',
  1080. dry_run=False,
  1081. )
  1082. def test_remove_data_source_dumps_swallows_umount_command_error():
  1083. config = {'lvm': {}}
  1084. flexmock(module).should_receive('get_logical_volumes').and_return(
  1085. (
  1086. module.Logical_volume(
  1087. name='lvolume1',
  1088. device_path='/dev/lvolume1',
  1089. mount_point='/mnt/lvolume1',
  1090. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1091. ),
  1092. module.Logical_volume(
  1093. name='lvolume2',
  1094. device_path='/dev/lvolume2',
  1095. mount_point='/mnt/lvolume2',
  1096. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1097. ),
  1098. )
  1099. )
  1100. flexmock(module.borgmatic.config.paths).should_receive(
  1101. 'replace_temporary_subdirectory_with_glob'
  1102. ).and_return('/run/borgmatic')
  1103. flexmock(module.glob).should_receive('glob').replace_with(
  1104. lambda path: [path.replace('*', 'b33f')]
  1105. )
  1106. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1107. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1108. flexmock(module.shutil).should_receive('rmtree')
  1109. flexmock(module).should_receive('unmount_snapshot').with_args(
  1110. 'umount',
  1111. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1112. ).and_raise(module.subprocess.CalledProcessError(1, 'wtf'))
  1113. flexmock(module).should_receive('unmount_snapshot').with_args(
  1114. 'umount',
  1115. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1116. ).once()
  1117. flexmock(module).should_receive('get_snapshots').and_return(
  1118. (
  1119. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  1120. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  1121. ),
  1122. )
  1123. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  1124. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  1125. module.remove_data_source_dumps(
  1126. hook_config=config['lvm'],
  1127. config=config,
  1128. borgmatic_runtime_directory='/run/borgmatic',
  1129. dry_run=False,
  1130. )
  1131. def test_remove_data_source_dumps_bails_for_missing_lvs_command():
  1132. config = {'lvm': {}}
  1133. flexmock(module).should_receive('get_logical_volumes').and_return(
  1134. (
  1135. module.Logical_volume(
  1136. name='lvolume1',
  1137. device_path='/dev/lvolume1',
  1138. mount_point='/mnt/lvolume1',
  1139. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1140. ),
  1141. module.Logical_volume(
  1142. name='lvolume2',
  1143. device_path='/dev/lvolume2',
  1144. mount_point='/mnt/lvolume2',
  1145. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1146. ),
  1147. )
  1148. )
  1149. flexmock(module.borgmatic.config.paths).should_receive(
  1150. 'replace_temporary_subdirectory_with_glob'
  1151. ).and_return('/run/borgmatic')
  1152. flexmock(module.glob).should_receive('glob').replace_with(
  1153. lambda path: [path.replace('*', 'b33f')]
  1154. )
  1155. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1156. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1157. flexmock(module.shutil).should_receive('rmtree')
  1158. flexmock(module).should_receive('unmount_snapshot').with_args(
  1159. 'umount',
  1160. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1161. ).once()
  1162. flexmock(module).should_receive('unmount_snapshot').with_args(
  1163. 'umount',
  1164. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1165. ).once()
  1166. flexmock(module).should_receive('get_snapshots').and_raise(FileNotFoundError)
  1167. flexmock(module).should_receive('remove_snapshot').never()
  1168. module.remove_data_source_dumps(
  1169. hook_config=config['lvm'],
  1170. config=config,
  1171. borgmatic_runtime_directory='/run/borgmatic',
  1172. dry_run=False,
  1173. )
  1174. def test_remove_data_source_dumps_bails_for_lvs_command_error():
  1175. config = {'lvm': {}}
  1176. flexmock(module).should_receive('get_logical_volumes').and_return(
  1177. (
  1178. module.Logical_volume(
  1179. name='lvolume1',
  1180. device_path='/dev/lvolume1',
  1181. mount_point='/mnt/lvolume1',
  1182. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1183. ),
  1184. module.Logical_volume(
  1185. name='lvolume2',
  1186. device_path='/dev/lvolume2',
  1187. mount_point='/mnt/lvolume2',
  1188. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1189. ),
  1190. )
  1191. )
  1192. flexmock(module.borgmatic.config.paths).should_receive(
  1193. 'replace_temporary_subdirectory_with_glob'
  1194. ).and_return('/run/borgmatic')
  1195. flexmock(module.glob).should_receive('glob').replace_with(
  1196. lambda path: [path.replace('*', 'b33f')]
  1197. )
  1198. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1199. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1200. flexmock(module.shutil).should_receive('rmtree')
  1201. flexmock(module).should_receive('unmount_snapshot').with_args(
  1202. 'umount',
  1203. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1204. ).once()
  1205. flexmock(module).should_receive('unmount_snapshot').with_args(
  1206. 'umount',
  1207. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1208. ).once()
  1209. flexmock(module).should_receive('get_snapshots').and_raise(
  1210. module.subprocess.CalledProcessError(1, 'wtf')
  1211. )
  1212. flexmock(module).should_receive('remove_snapshot').never()
  1213. module.remove_data_source_dumps(
  1214. hook_config=config['lvm'],
  1215. config=config,
  1216. borgmatic_runtime_directory='/run/borgmatic',
  1217. dry_run=False,
  1218. )
  1219. def test_remove_data_source_with_dry_run_skips_snapshot_unmount_and_delete():
  1220. config = {'lvm': {}}
  1221. flexmock(module).should_receive('get_logical_volumes').and_return(
  1222. (
  1223. module.Logical_volume(
  1224. name='lvolume1',
  1225. device_path='/dev/lvolume1',
  1226. mount_point='/mnt/lvolume1',
  1227. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1228. ),
  1229. module.Logical_volume(
  1230. name='lvolume2',
  1231. device_path='/dev/lvolume2',
  1232. mount_point='/mnt/lvolume2',
  1233. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1234. ),
  1235. )
  1236. )
  1237. flexmock(module.borgmatic.config.paths).should_receive(
  1238. 'replace_temporary_subdirectory_with_glob'
  1239. ).and_return('/run/borgmatic')
  1240. flexmock(module.glob).should_receive('glob').replace_with(lambda path: [path])
  1241. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1242. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1243. flexmock(module.shutil).should_receive('rmtree').never()
  1244. flexmock(module).should_receive('unmount_snapshot').never()
  1245. flexmock(module).should_receive('get_snapshots').and_return(
  1246. (
  1247. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  1248. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  1249. module.Snapshot('nonborgmatic', '/dev/nonborgmatic'),
  1250. ),
  1251. ).once()
  1252. flexmock(module).should_receive('remove_snapshot').never()
  1253. module.remove_data_source_dumps(
  1254. hook_config=config['lvm'],
  1255. config=config,
  1256. borgmatic_runtime_directory='/run/borgmatic',
  1257. dry_run=True,
  1258. )