test_lvm.py 47 KB

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