test_lvm.py 53 KB

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