2
0

test_lvm.py 51 KB

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