Skip to content

Commit 89d172f

Browse files
committed
Add mdadm and lvm grains back in to core
Adds the mdadm and lvm grains back in to core. The were removed as part of the community module migration, but they provide key functionality on linux systems and the associated execution and state modules were not removed.
1 parent 904d08b commit 89d172f

File tree

7 files changed

+354
-0
lines changed

7 files changed

+354
-0
lines changed

changelog/68470.fixed.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Adds `mdadm` and `lvm` grains modules back in to core.
2+
3+
Restores the modules that had been removed as part of the community module
4+
migration. They are core bits of functionality and the associated execution and
5+
states modules had not been removed.

doc/ref/grains/all/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ grains modules
1313
core
1414
disks
1515
extra
16+
lvm
17+
mdadm
1618
minion_process
1719
opts
1820
package
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
salt.grains.lvm
2+
===============
3+
4+
.. automodule:: salt.grains.lvm
5+
:members:
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
salt.grains.mdadm
2+
=================
3+
4+
.. automodule:: salt.grains.mdadm
5+
:members:

salt/grains/lvm.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""
2+
Detect LVM Volumes
3+
"""
4+
5+
import logging
6+
7+
import salt.modules.cmdmod
8+
import salt.utils.files
9+
import salt.utils.path
10+
import salt.utils.platform
11+
12+
__salt__ = {
13+
"cmd.run": salt.modules.cmdmod._run_quiet,
14+
"cmd.run_all": salt.modules.cmdmod._run_all_quiet,
15+
}
16+
17+
log = logging.getLogger(__name__)
18+
19+
20+
def lvm():
21+
"""
22+
Return list of LVM devices
23+
"""
24+
if salt.utils.platform.is_linux():
25+
return _linux_lvm()
26+
elif salt.utils.platform.is_aix():
27+
return _aix_lvm()
28+
else:
29+
log.trace("LVM grain does not support this OS")
30+
31+
32+
def _linux_lvm():
33+
ret = {}
34+
cmd = salt.utils.path.which("lvm")
35+
if cmd:
36+
vgs = __salt__["cmd.run_all"](f"{cmd} vgs -o vg_name --noheadings")
37+
38+
for vg in vgs["stdout"].splitlines():
39+
vg = vg.strip()
40+
ret[vg] = []
41+
lvs = __salt__["cmd.run_all"](f"{cmd} lvs -o lv_name --noheadings {vg}")
42+
for lv in lvs["stdout"].splitlines():
43+
ret[vg].append(lv.strip())
44+
45+
return {"lvm": ret}
46+
else:
47+
log.trace("No LVM installed")
48+
49+
50+
def _aix_lvm():
51+
ret = {}
52+
cmd = salt.utils.path.which("lsvg")
53+
vgs = __salt__["cmd.run"](f"{cmd}")
54+
55+
for vg in vgs.splitlines():
56+
ret[vg] = []
57+
lvs = __salt__["cmd.run"](f"{cmd} -l {vg}")
58+
for lvline in lvs.splitlines()[2:]:
59+
lv = lvline.split(" ", 1)[0]
60+
ret[vg].append(lv)
61+
62+
return {"lvm": ret}

salt/grains/mdadm.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
Detect MDADM RAIDs
3+
"""
4+
5+
import logging
6+
7+
import salt.utils.files
8+
9+
log = logging.getLogger(__name__)
10+
11+
12+
def mdadm():
13+
"""
14+
Return list of mdadm devices
15+
"""
16+
devices = set()
17+
try:
18+
with salt.utils.files.fopen("/proc/mdstat", "r") as mdstat:
19+
for line in mdstat:
20+
line = salt.utils.stringutils.to_unicode(line)
21+
if line.startswith("Personalities : "):
22+
continue
23+
if line.startswith("unused devices:"):
24+
continue
25+
if " : " in line:
26+
devices.add(line.split(" : ")[0])
27+
except OSError:
28+
return {}
29+
30+
devices = sorted(devices)
31+
if devices:
32+
log.trace("mdadm devices detected: %s", ", ".join(devices))
33+
34+
return {"mdadm": devices}
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
"""
2+
:codeauthor: :email:`Shane Lee <[email protected]>`
3+
"""
4+
5+
import pytest
6+
7+
import salt.grains.lvm as lvm
8+
from tests.support.mock import MagicMock, patch
9+
10+
11+
@pytest.fixture
12+
def configure_loader_modules():
13+
return {
14+
lvm: {"__salt__": {}},
15+
}
16+
17+
18+
def test__linux_lvm():
19+
"""
20+
Test grains._linux_lvm, normal return
21+
Should return a populated dictionary
22+
"""
23+
24+
vgs_out = {"pid": 123, "retcode": 0, "stdout": " vg00\n vg01", "stderr": ""}
25+
lvs_out_vg00 = {
26+
"pid": 456,
27+
"retcode": 0,
28+
"stdout": " root\n swap\n tmp \n usr \n var",
29+
"stderr": "",
30+
}
31+
lvs_out_vg01 = {"pid": 789, "retcode": 0, "stdout": " opt", "stderr": ""}
32+
cmd_out = MagicMock(
33+
autospec=True, side_effect=[vgs_out, lvs_out_vg00, lvs_out_vg01]
34+
)
35+
36+
patch_which = patch(
37+
"salt.utils.path.which", autospec=True, return_value="/usr/sbin/lvm"
38+
)
39+
patch_cmd_lvm = patch.dict(lvm.__salt__, {"cmd.run_all": cmd_out})
40+
with patch_which, patch_cmd_lvm:
41+
ret = lvm._linux_lvm()
42+
43+
assert ret == {
44+
"lvm": {"vg00": ["root", "swap", "tmp", "usr", "var"], "vg01": ["opt"]}
45+
}, ret
46+
47+
48+
def test__linux_lvm_with_WARNINGs():
49+
"""
50+
Test grains._linux_lvm, with WARNINGs in lvm command output
51+
Should return a populated dictionary
52+
"""
53+
54+
vgs_out = {
55+
"pid": 123,
56+
"retcode": 0,
57+
"stdout": " vg00\n vg01",
58+
"stderr": "WARNING: Something wrong is not right",
59+
}
60+
lvs_out_vg00 = {
61+
"pid": 456,
62+
"retcode": 0,
63+
"stdout": " root\n swap\n tmp \n usr \n var",
64+
"stderr": "WARNING: Something wrong is not right",
65+
}
66+
lvs_out_vg01 = {
67+
"pid": 789,
68+
"retcode": 0,
69+
"stdout": " opt",
70+
"stderr": "WARNING: Something wrong is not right",
71+
}
72+
cmd_out = MagicMock(
73+
autospec=True, side_effect=[vgs_out, lvs_out_vg00, lvs_out_vg01]
74+
)
75+
76+
patch_which = patch(
77+
"salt.utils.path.which", autospec=True, return_value="/usr/sbin/lvm"
78+
)
79+
patch_cmd_lvm = patch.dict(lvm.__salt__, {"cmd.run_all": cmd_out})
80+
with patch_which, patch_cmd_lvm:
81+
ret = lvm._linux_lvm()
82+
83+
assert ret == {
84+
"lvm": {"vg00": ["root", "swap", "tmp", "usr", "var"], "vg01": ["opt"]}
85+
}, ret
86+
87+
88+
def test__linux_lvm_with_non_zero_exit_codes():
89+
"""
90+
Test grains._linux_lvm, with non-zero exit codes for lvm command
91+
Should return a populated dictionary
92+
"""
93+
94+
vgs_out = {
95+
"pid": 123,
96+
"retcode": 5,
97+
"stdout": " vg00\n vg01",
98+
"stderr": " Skipping clustered volume vgcluster\n Skipping volume group vgcluster",
99+
}
100+
lvs_out_vg00 = {
101+
"pid": 456,
102+
"retcode": 0,
103+
"stdout": " root\n swap\n tmp \n usr \n var",
104+
"stderr": "",
105+
}
106+
lvs_out_vg01 = {"pid": 789, "retcode": 0, "stdout": " opt", "stderr": ""}
107+
cmd_out = MagicMock(
108+
autospec=True, side_effect=[vgs_out, lvs_out_vg00, lvs_out_vg01]
109+
)
110+
111+
patch_which = patch(
112+
"salt.utils.path.which", autospec=True, return_value="/usr/sbin/lvm"
113+
)
114+
patch_cmd_lvm = patch.dict(lvm.__salt__, {"cmd.run_all": cmd_out})
115+
with patch_which, patch_cmd_lvm:
116+
ret = lvm._linux_lvm()
117+
118+
assert ret == {
119+
"lvm": {"vg00": ["root", "swap", "tmp", "usr", "var"], "vg01": ["opt"]}
120+
}, ret
121+
122+
123+
def test__linux_lvm_no_lvm():
124+
"""
125+
Test grains._linux_lvm, no lvm installed
126+
Should return nothing
127+
"""
128+
129+
vgs_out = {"pid": 123, "retcode": 0, "stdout": " vg00\n vg01", "stderr": ""}
130+
lvs_out_vg00 = {
131+
"pid": 456,
132+
"retcode": 0,
133+
"stdout": " root\n swap\n tmp \n usr \n var",
134+
"stderr": "",
135+
}
136+
lvs_out_vg01 = {"pid": 789, "retcode": 0, "stdout": " opt", "stderr": ""}
137+
cmd_out = MagicMock(
138+
autospec=True, side_effect=[vgs_out, lvs_out_vg00, lvs_out_vg01]
139+
)
140+
141+
patch_which = patch("salt.utils.path.which", autospec=True, return_value="")
142+
patch_cmd_lvm = patch.dict(lvm.__salt__, {"cmd.run_all": cmd_out})
143+
with patch_which, patch_cmd_lvm:
144+
ret = lvm._linux_lvm()
145+
146+
assert ret is None, ret
147+
148+
149+
def test__linux_lvm_no_volume_groups():
150+
"""
151+
Test grains._linux_lvm, lvm is installed but no volume groups created.
152+
Should return a dictionary only with the header
153+
"""
154+
155+
vgs_out = {"pid": 123, "retcode": 0, "stdout": "", "stderr": ""}
156+
cmd_out = MagicMock(autospec=True, side_effect=[vgs_out])
157+
158+
patch_which = patch(
159+
"salt.utils.path.which", autospec=True, return_value="/usr/sbin/lvm"
160+
)
161+
patch_cmd_lvm = patch.dict(lvm.__salt__, {"cmd.run_all": cmd_out})
162+
with patch_which, patch_cmd_lvm:
163+
ret = lvm._linux_lvm()
164+
165+
assert ret == {"lvm": {}}, ret
166+
167+
168+
def test__linux_lvm_no_logical_volumes():
169+
"""
170+
Test grains._linux_lvm, lvm is installed, volume groups created but
171+
no logical volumes present.
172+
Should return a dictionary only with the header
173+
"""
174+
175+
vgs_out = {"pid": 123, "retcode": 0, "stdout": " vg00\n vg01", "stderr": ""}
176+
lvs_out = {"pid": 456, "retcode": 0, "stdout": "", "stderr": ""}
177+
cmd_out = MagicMock(autospec=True, side_effect=[vgs_out, lvs_out, lvs_out])
178+
179+
patch_which = patch(
180+
"salt.utils.path.which", autospec=True, return_value="/usr/sbin/lvm"
181+
)
182+
patch_cmd_lvm = patch.dict(lvm.__salt__, {"cmd.run_all": cmd_out})
183+
with patch_which, patch_cmd_lvm:
184+
ret = lvm._linux_lvm()
185+
186+
assert ret == {"lvm": {"vg00": [], "vg01": []}}, ret
187+
188+
189+
def test__aix_lvm():
190+
"""
191+
Test grains._aix_lvm, normal return
192+
Should return a populated dictionary
193+
"""
194+
195+
lsvg_out = "rootvg\nothervg"
196+
lsvg_out_rootvg = (
197+
"rootvg:\nLV NAME TYPE LPs PPs PVs LV STATE "
198+
" MOUNT POINT\nhd5 boot 1 1 1 "
199+
" closed/syncd N/A\nhd6 paging 32 32 1 "
200+
" open/syncd N/A\nhd8 jfs2log 1 1 1 "
201+
" open/syncd N/A\nhd4 jfs2 32 32 1 "
202+
" open/syncd /\nhd2 jfs2 16 16 1 "
203+
" open/syncd /usr\nhd9var jfs2 32 32 1 "
204+
" open/syncd /var\nhd3 jfs2 32 32 1 "
205+
" open/syncd /tmp\nhd1 jfs2 16 16 1 "
206+
" open/syncd /home\nhd10opt jfs2 16 16 1 "
207+
" open/syncd /opt"
208+
)
209+
lsvg_out_othervg = (
210+
"othervg:\nLV NAME TYPE LPs PPs PVs LV STATE "
211+
" MOUNT POINT\nloglv01 jfs2log 1 1 1 "
212+
" open/syncd N/A\ndatalv jfs2 16 16 1 "
213+
" open/syncd /data"
214+
)
215+
cmd_out = MagicMock(
216+
autospec=True, side_effect=[lsvg_out, lsvg_out_rootvg, lsvg_out_othervg]
217+
)
218+
219+
patch_which = patch(
220+
"salt.utils.path.which", autospec=True, return_value="/usr/sbin/lsvg"
221+
)
222+
patch_cmd_lvm = patch.dict(lvm.__salt__, {"cmd.run": cmd_out})
223+
with patch_which, patch_cmd_lvm:
224+
ret = lvm._aix_lvm()
225+
226+
assert ret == {
227+
"lvm": {
228+
"rootvg": [
229+
"hd5",
230+
"hd6",
231+
"hd8",
232+
"hd4",
233+
"hd2",
234+
"hd9var",
235+
"hd3",
236+
"hd1",
237+
"hd10opt",
238+
],
239+
"othervg": ["loglv01", "datalv"],
240+
}
241+
}, ret

0 commit comments

Comments
 (0)