test_lvm.py 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434
  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. dry_run=False,
  844. )
  845. def test_remove_data_source_dumps_bails_for_missing_lvm_configuration():
  846. flexmock(module).should_receive('get_logical_volumes').never()
  847. flexmock(module.borgmatic.config.paths).should_receive(
  848. 'replace_temporary_subdirectory_with_glob',
  849. ).never()
  850. flexmock(module).should_receive('unmount_snapshot').never()
  851. flexmock(module).should_receive('remove_snapshot').never()
  852. module.remove_data_source_dumps(
  853. hook_config=None,
  854. config={'source_directories': '/mnt/lvolume'},
  855. borgmatic_runtime_directory='/run/borgmatic',
  856. dry_run=False,
  857. )
  858. def test_remove_data_source_dumps_bails_for_missing_lsblk_command():
  859. config = {'lvm': {}}
  860. flexmock(module).should_receive('get_logical_volumes').and_raise(FileNotFoundError)
  861. flexmock(module.borgmatic.config.paths).should_receive(
  862. 'replace_temporary_subdirectory_with_glob',
  863. ).never()
  864. flexmock(module).should_receive('unmount_snapshot').never()
  865. flexmock(module).should_receive('remove_snapshot').never()
  866. module.remove_data_source_dumps(
  867. hook_config=config['lvm'],
  868. config=config,
  869. borgmatic_runtime_directory='/run/borgmatic',
  870. dry_run=False,
  871. )
  872. def test_remove_data_source_dumps_bails_for_lsblk_command_error():
  873. config = {'lvm': {}}
  874. flexmock(module).should_receive('get_logical_volumes').and_raise(
  875. module.subprocess.CalledProcessError(1, 'wtf'),
  876. )
  877. flexmock(module.borgmatic.config.paths).should_receive(
  878. 'replace_temporary_subdirectory_with_glob',
  879. ).never()
  880. flexmock(module).should_receive('unmount_snapshot').never()
  881. flexmock(module).should_receive('remove_snapshot').never()
  882. module.remove_data_source_dumps(
  883. hook_config=config['lvm'],
  884. config=config,
  885. borgmatic_runtime_directory='/run/borgmatic',
  886. dry_run=False,
  887. )
  888. def test_remove_data_source_dumps_with_missing_snapshot_directory_skips_unmount():
  889. config = {'lvm': {}}
  890. flexmock(module).should_receive('get_logical_volumes').and_return(
  891. (
  892. module.Logical_volume(
  893. name='lvolume1',
  894. device_path='/dev/lvolume1',
  895. mount_point='/mnt/lvolume1',
  896. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  897. ),
  898. module.Logical_volume(
  899. name='lvolume2',
  900. device_path='/dev/lvolume2',
  901. mount_point='/mnt/lvolume2',
  902. contained_patterns=(Pattern('/mnt/lvolume2'),),
  903. ),
  904. ),
  905. )
  906. flexmock(module.borgmatic.config.paths).should_receive(
  907. 'replace_temporary_subdirectory_with_glob',
  908. ).and_return('/run/borgmatic')
  909. flexmock(module.glob).should_receive('glob').replace_with(
  910. lambda path: [path.replace('*', 'b33f')],
  911. )
  912. flexmock(module.os.path).should_receive('isdir').with_args(
  913. '/run/borgmatic/lvm_snapshots/b33f',
  914. ).and_return(False)
  915. flexmock(module.shutil).should_receive('rmtree').never()
  916. flexmock(module).should_receive('unmount_snapshot').never()
  917. flexmock(module).should_receive('get_snapshots').and_return(
  918. (
  919. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  920. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  921. ),
  922. )
  923. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  924. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  925. module.remove_data_source_dumps(
  926. hook_config=config['lvm'],
  927. config=config,
  928. borgmatic_runtime_directory='/run/borgmatic',
  929. dry_run=False,
  930. )
  931. def test_remove_data_source_dumps_with_missing_snapshot_mount_path_skips_unmount():
  932. config = {'lvm': {}}
  933. flexmock(module).should_receive('get_logical_volumes').and_return(
  934. (
  935. module.Logical_volume(
  936. name='lvolume1',
  937. device_path='/dev/lvolume1',
  938. mount_point='/mnt/lvolume1',
  939. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  940. ),
  941. module.Logical_volume(
  942. name='lvolume2',
  943. device_path='/dev/lvolume2',
  944. mount_point='/mnt/lvolume2',
  945. contained_patterns=(Pattern('/mnt/lvolume2'),),
  946. ),
  947. ),
  948. )
  949. flexmock(module.borgmatic.config.paths).should_receive(
  950. 'replace_temporary_subdirectory_with_glob',
  951. ).and_return('/run/borgmatic')
  952. flexmock(module.glob).should_receive('glob').replace_with(
  953. lambda path: [path.replace('*', 'b33f')],
  954. )
  955. flexmock(module.os.path).should_receive('isdir').with_args(
  956. '/run/borgmatic/lvm_snapshots/b33f',
  957. ).and_return(True)
  958. flexmock(module.os.path).should_receive('isdir').with_args(
  959. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  960. ).and_return(False)
  961. flexmock(module.os.path).should_receive('isdir').with_args(
  962. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  963. ).and_return(True)
  964. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  965. flexmock(module.shutil).should_receive('rmtree')
  966. flexmock(module).should_receive('unmount_snapshot').with_args(
  967. 'umount',
  968. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  969. ).never()
  970. flexmock(module).should_receive('unmount_snapshot').with_args(
  971. 'umount',
  972. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  973. ).once()
  974. flexmock(module).should_receive('get_snapshots').and_return(
  975. (
  976. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  977. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  978. ),
  979. )
  980. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  981. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  982. module.remove_data_source_dumps(
  983. hook_config=config['lvm'],
  984. config=config,
  985. borgmatic_runtime_directory='/run/borgmatic',
  986. dry_run=False,
  987. )
  988. def test_remove_data_source_dumps_with_empty_snapshot_mount_path_skips_unmount():
  989. config = {'lvm': {}}
  990. flexmock(module).should_receive('get_logical_volumes').and_return(
  991. (
  992. module.Logical_volume(
  993. name='lvolume1',
  994. device_path='/dev/lvolume1',
  995. mount_point='/mnt/lvolume1',
  996. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  997. ),
  998. module.Logical_volume(
  999. name='lvolume2',
  1000. device_path='/dev/lvolume2',
  1001. mount_point='/mnt/lvolume2',
  1002. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1003. ),
  1004. ),
  1005. )
  1006. flexmock(module.borgmatic.config.paths).should_receive(
  1007. 'replace_temporary_subdirectory_with_glob',
  1008. ).and_return('/run/borgmatic')
  1009. flexmock(module.glob).should_receive('glob').replace_with(
  1010. lambda path: [path.replace('*', 'b33f')],
  1011. )
  1012. flexmock(module.os.path).should_receive('isdir').with_args(
  1013. '/run/borgmatic/lvm_snapshots/b33f',
  1014. ).and_return(True)
  1015. flexmock(module.os.path).should_receive('isdir').with_args(
  1016. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1017. ).and_return(True)
  1018. flexmock(module.os).should_receive('listdir').with_args(
  1019. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1020. ).and_return([])
  1021. flexmock(module.os.path).should_receive('isdir').with_args(
  1022. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1023. ).and_return(True)
  1024. flexmock(module.os).should_receive('listdir').with_args(
  1025. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1026. ).and_return(['file.txt'])
  1027. flexmock(module.shutil).should_receive('rmtree')
  1028. flexmock(module).should_receive('unmount_snapshot').with_args(
  1029. 'umount',
  1030. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1031. ).never()
  1032. flexmock(module).should_receive('unmount_snapshot').with_args(
  1033. 'umount',
  1034. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1035. ).once()
  1036. flexmock(module).should_receive('get_snapshots').and_return(
  1037. (
  1038. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  1039. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  1040. ),
  1041. )
  1042. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  1043. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  1044. module.remove_data_source_dumps(
  1045. hook_config=config['lvm'],
  1046. config=config,
  1047. borgmatic_runtime_directory='/run/borgmatic',
  1048. dry_run=False,
  1049. )
  1050. def test_remove_data_source_dumps_with_successful_mount_point_removal_skips_unmount():
  1051. config = {'lvm': {}}
  1052. flexmock(module).should_receive('get_logical_volumes').and_return(
  1053. (
  1054. module.Logical_volume(
  1055. name='lvolume1',
  1056. device_path='/dev/lvolume1',
  1057. mount_point='/mnt/lvolume1',
  1058. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1059. ),
  1060. module.Logical_volume(
  1061. name='lvolume2',
  1062. device_path='/dev/lvolume2',
  1063. mount_point='/mnt/lvolume2',
  1064. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1065. ),
  1066. ),
  1067. )
  1068. flexmock(module.borgmatic.config.paths).should_receive(
  1069. 'replace_temporary_subdirectory_with_glob',
  1070. ).and_return('/run/borgmatic')
  1071. flexmock(module.glob).should_receive('glob').replace_with(
  1072. lambda path: [path.replace('*', 'b33f')],
  1073. )
  1074. flexmock(module.os.path).should_receive('isdir').with_args(
  1075. '/run/borgmatic/lvm_snapshots/b33f',
  1076. ).and_return(True)
  1077. flexmock(module.os.path).should_receive('isdir').with_args(
  1078. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1079. ).and_return(True).and_return(False)
  1080. flexmock(module.os.path).should_receive('isdir').with_args(
  1081. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1082. ).and_return(True).and_return(True)
  1083. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1084. flexmock(module.shutil).should_receive('rmtree')
  1085. flexmock(module).should_receive('unmount_snapshot').with_args(
  1086. 'umount',
  1087. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1088. ).never()
  1089. flexmock(module).should_receive('unmount_snapshot').with_args(
  1090. 'umount',
  1091. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1092. ).once()
  1093. flexmock(module).should_receive('get_snapshots').and_return(
  1094. (
  1095. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  1096. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  1097. ),
  1098. )
  1099. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  1100. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  1101. module.remove_data_source_dumps(
  1102. hook_config=config['lvm'],
  1103. config=config,
  1104. borgmatic_runtime_directory='/run/borgmatic',
  1105. dry_run=False,
  1106. )
  1107. def test_remove_data_source_dumps_bails_for_missing_umount_command():
  1108. config = {'lvm': {}}
  1109. flexmock(module).should_receive('get_logical_volumes').and_return(
  1110. (
  1111. module.Logical_volume(
  1112. name='lvolume1',
  1113. device_path='/dev/lvolume1',
  1114. mount_point='/mnt/lvolume1',
  1115. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1116. ),
  1117. module.Logical_volume(
  1118. name='lvolume2',
  1119. device_path='/dev/lvolume2',
  1120. mount_point='/mnt/lvolume2',
  1121. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1122. ),
  1123. ),
  1124. )
  1125. flexmock(module.borgmatic.config.paths).should_receive(
  1126. 'replace_temporary_subdirectory_with_glob',
  1127. ).and_return('/run/borgmatic')
  1128. flexmock(module.glob).should_receive('glob').replace_with(
  1129. lambda path: [path.replace('*', 'b33f')],
  1130. )
  1131. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1132. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1133. flexmock(module.shutil).should_receive('rmtree')
  1134. flexmock(module).should_receive('unmount_snapshot').with_args(
  1135. 'umount',
  1136. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1137. ).and_raise(FileNotFoundError)
  1138. flexmock(module).should_receive('unmount_snapshot').with_args(
  1139. 'umount',
  1140. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1141. ).never()
  1142. flexmock(module).should_receive('get_snapshots').never()
  1143. flexmock(module).should_receive('remove_snapshot').never()
  1144. module.remove_data_source_dumps(
  1145. hook_config=config['lvm'],
  1146. config=config,
  1147. borgmatic_runtime_directory='/run/borgmatic',
  1148. dry_run=False,
  1149. )
  1150. def test_remove_data_source_dumps_swallows_umount_command_error():
  1151. config = {'lvm': {}}
  1152. flexmock(module).should_receive('get_logical_volumes').and_return(
  1153. (
  1154. module.Logical_volume(
  1155. name='lvolume1',
  1156. device_path='/dev/lvolume1',
  1157. mount_point='/mnt/lvolume1',
  1158. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1159. ),
  1160. module.Logical_volume(
  1161. name='lvolume2',
  1162. device_path='/dev/lvolume2',
  1163. mount_point='/mnt/lvolume2',
  1164. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1165. ),
  1166. ),
  1167. )
  1168. flexmock(module.borgmatic.config.paths).should_receive(
  1169. 'replace_temporary_subdirectory_with_glob',
  1170. ).and_return('/run/borgmatic')
  1171. flexmock(module.glob).should_receive('glob').replace_with(
  1172. lambda path: [path.replace('*', 'b33f')],
  1173. )
  1174. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1175. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1176. flexmock(module.shutil).should_receive('rmtree')
  1177. flexmock(module).should_receive('unmount_snapshot').with_args(
  1178. 'umount',
  1179. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1180. ).and_raise(module.subprocess.CalledProcessError(1, 'wtf'))
  1181. flexmock(module).should_receive('unmount_snapshot').with_args(
  1182. 'umount',
  1183. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1184. ).once()
  1185. flexmock(module).should_receive('get_snapshots').and_return(
  1186. (
  1187. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  1188. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  1189. ),
  1190. )
  1191. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume1').once()
  1192. flexmock(module).should_receive('remove_snapshot').with_args('lvremove', '/dev/lvolume2').once()
  1193. module.remove_data_source_dumps(
  1194. hook_config=config['lvm'],
  1195. config=config,
  1196. borgmatic_runtime_directory='/run/borgmatic',
  1197. dry_run=False,
  1198. )
  1199. def test_remove_data_source_dumps_bails_for_missing_lvs_command():
  1200. config = {'lvm': {}}
  1201. flexmock(module).should_receive('get_logical_volumes').and_return(
  1202. (
  1203. module.Logical_volume(
  1204. name='lvolume1',
  1205. device_path='/dev/lvolume1',
  1206. mount_point='/mnt/lvolume1',
  1207. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1208. ),
  1209. module.Logical_volume(
  1210. name='lvolume2',
  1211. device_path='/dev/lvolume2',
  1212. mount_point='/mnt/lvolume2',
  1213. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1214. ),
  1215. ),
  1216. )
  1217. flexmock(module.borgmatic.config.paths).should_receive(
  1218. 'replace_temporary_subdirectory_with_glob',
  1219. ).and_return('/run/borgmatic')
  1220. flexmock(module.glob).should_receive('glob').replace_with(
  1221. lambda path: [path.replace('*', 'b33f')],
  1222. )
  1223. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1224. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1225. flexmock(module.shutil).should_receive('rmtree')
  1226. flexmock(module).should_receive('unmount_snapshot').with_args(
  1227. 'umount',
  1228. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1229. ).once()
  1230. flexmock(module).should_receive('unmount_snapshot').with_args(
  1231. 'umount',
  1232. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1233. ).once()
  1234. flexmock(module).should_receive('get_snapshots').and_raise(FileNotFoundError)
  1235. flexmock(module).should_receive('remove_snapshot').never()
  1236. module.remove_data_source_dumps(
  1237. hook_config=config['lvm'],
  1238. config=config,
  1239. borgmatic_runtime_directory='/run/borgmatic',
  1240. dry_run=False,
  1241. )
  1242. def test_remove_data_source_dumps_bails_for_lvs_command_error():
  1243. config = {'lvm': {}}
  1244. flexmock(module).should_receive('get_logical_volumes').and_return(
  1245. (
  1246. module.Logical_volume(
  1247. name='lvolume1',
  1248. device_path='/dev/lvolume1',
  1249. mount_point='/mnt/lvolume1',
  1250. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1251. ),
  1252. module.Logical_volume(
  1253. name='lvolume2',
  1254. device_path='/dev/lvolume2',
  1255. mount_point='/mnt/lvolume2',
  1256. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1257. ),
  1258. ),
  1259. )
  1260. flexmock(module.borgmatic.config.paths).should_receive(
  1261. 'replace_temporary_subdirectory_with_glob',
  1262. ).and_return('/run/borgmatic')
  1263. flexmock(module.glob).should_receive('glob').replace_with(
  1264. lambda path: [path.replace('*', 'b33f')],
  1265. )
  1266. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1267. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1268. flexmock(module.shutil).should_receive('rmtree')
  1269. flexmock(module).should_receive('unmount_snapshot').with_args(
  1270. 'umount',
  1271. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
  1272. ).once()
  1273. flexmock(module).should_receive('unmount_snapshot').with_args(
  1274. 'umount',
  1275. '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
  1276. ).once()
  1277. flexmock(module).should_receive('get_snapshots').and_raise(
  1278. module.subprocess.CalledProcessError(1, 'wtf'),
  1279. )
  1280. flexmock(module).should_receive('remove_snapshot').never()
  1281. module.remove_data_source_dumps(
  1282. hook_config=config['lvm'],
  1283. config=config,
  1284. borgmatic_runtime_directory='/run/borgmatic',
  1285. dry_run=False,
  1286. )
  1287. def test_remove_data_source_with_dry_run_skips_snapshot_unmount_and_delete():
  1288. config = {'lvm': {}}
  1289. flexmock(module).should_receive('get_logical_volumes').and_return(
  1290. (
  1291. module.Logical_volume(
  1292. name='lvolume1',
  1293. device_path='/dev/lvolume1',
  1294. mount_point='/mnt/lvolume1',
  1295. contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
  1296. ),
  1297. module.Logical_volume(
  1298. name='lvolume2',
  1299. device_path='/dev/lvolume2',
  1300. mount_point='/mnt/lvolume2',
  1301. contained_patterns=(Pattern('/mnt/lvolume2'),),
  1302. ),
  1303. ),
  1304. )
  1305. flexmock(module.borgmatic.config.paths).should_receive(
  1306. 'replace_temporary_subdirectory_with_glob',
  1307. ).and_return('/run/borgmatic')
  1308. flexmock(module.glob).should_receive('glob').replace_with(lambda path: [path])
  1309. flexmock(module.os.path).should_receive('isdir').and_return(True)
  1310. flexmock(module.os).should_receive('listdir').and_return(['file.txt'])
  1311. flexmock(module.shutil).should_receive('rmtree').never()
  1312. flexmock(module).should_receive('unmount_snapshot').never()
  1313. flexmock(module).should_receive('get_snapshots').and_return(
  1314. (
  1315. module.Snapshot('lvolume1_borgmatic-1234', '/dev/lvolume1'),
  1316. module.Snapshot('lvolume2_borgmatic-1234', '/dev/lvolume2'),
  1317. module.Snapshot('nonborgmatic', '/dev/nonborgmatic'),
  1318. ),
  1319. ).once()
  1320. flexmock(module).should_receive('remove_snapshot').never()
  1321. module.remove_data_source_dumps(
  1322. hook_config=config['lvm'],
  1323. config=config,
  1324. borgmatic_runtime_directory='/run/borgmatic',
  1325. dry_run=True,
  1326. )