test_lvm.py 54 KB

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