2
0

test_lvm.py 40 KB

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