Fix an issue with unresolved grains on Windows

Some WMI calls are returning empty, maybe something is corrupted
underneath. This will just return None for grains that can't be
collected using WMI.
This commit is contained in:
Shane Lee 2024-02-16 11:05:08 -07:00 committed by Daniel Wozniak
parent 661c216fa4
commit 75db735867
4 changed files with 399 additions and 132 deletions

2
changelog/65154.fixed.md Normal file
View file

@ -0,0 +1,2 @@
Fix an issue where the minion would crash on Windows if some of the grains
failed to resolve

View file

@ -729,46 +729,62 @@ def _windows_virtual(osdata):
if osdata["kernel"] != "Windows":
return grains
grains["virtual"] = osdata.get("virtual", "physical")
# Set the default virtual environment to physical, meaning not a VM
grains["virtual"] = "physical"
# It is possible that the 'manufacturer' and/or 'productname' grains
# exist but have a value of None.
# It is possible that the 'manufacturer' and/or 'productname' grains exist
# but have a value of None
manufacturer = osdata.get("manufacturer", "")
if manufacturer is None:
manufacturer = ""
productname = osdata.get("productname", "")
if productname is None:
productname = ""
product_name = osdata.get("productname", "")
if product_name is None:
product_name = ""
bios_string = osdata.get("biosstring", "")
if bios_string is None:
bios_string = ""
if "QEMU" in manufacturer:
# FIXME: Make this detect between kvm or qemu
grains["virtual"] = "kvm"
if "Bochs" in manufacturer:
elif "VRTUAL" in bios_string: # (not a typo)
grains["virtual"] = "HyperV"
elif "A M I" in bios_string:
grains["virtual"] = "VirtualPC"
elif "Xen" in bios_string:
grains["virtual"] = "Xen"
if "HVM domU" in product_name:
grains["virtual_subtype"] = "HVM domU"
elif "AMAZON" in bios_string:
grains["virtual"] = "EC2"
elif "Bochs" in manufacturer:
grains["virtual"] = "kvm"
# Product Name: (oVirt) www.ovirt.org
# Red Hat Community virtualization Project based on kvm
elif "oVirt" in productname:
elif "oVirt" in product_name:
grains["virtual"] = "kvm"
grains["virtual_subtype"] = "oVirt"
# Red Hat Enterprise Virtualization
elif "RHEV Hypervisor" in productname:
elif "RHEV Hypervisor" in product_name:
grains["virtual"] = "kvm"
grains["virtual_subtype"] = "rhev"
# Product Name: VirtualBox
elif "VirtualBox" in productname:
elif "VirtualBox" in product_name:
grains["virtual"] = "VirtualBox"
# Product Name: VMware Virtual Platform
elif "VMware" in productname:
elif "VMware" in product_name:
grains["virtual"] = "VMware"
# Manufacturer: Microsoft Corporation
# Product Name: Virtual Machine
elif "Microsoft" in manufacturer and "Virtual Machine" in productname:
elif "Microsoft" in manufacturer and "Virtual Machine" in product_name:
grains["virtual"] = "VirtualPC"
elif "OpenStack" in product_name:
grains["virtual"] = "OpenStack"
# Manufacturer: Parallels Software International Inc.
elif "Parallels" in manufacturer:
grains["virtual"] = "Parallels"
# Apache CloudStack
elif "CloudStack KVM Hypervisor" in productname:
elif "CloudStack KVM Hypervisor" in product_name:
grains["virtual"] = "kvm"
grains["virtual_subtype"] = "cloudstack"
return grains
@ -1502,88 +1518,143 @@ def _windows_platform_data():
if not HAS_WMI:
return {}
grains = {}
with salt.utils.winapi.Com():
wmi_c = wmi.WMI()
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394102%28v=vs.85%29.aspx
systeminfo = wmi_c.Win32_ComputerSystem()[0]
# https://msdn.microsoft.com/en-us/library/aa394239(v=vs.85).aspx
osinfo = wmi_c.Win32_OperatingSystem()[0]
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394077(v=vs.85).aspx
biosinfo = wmi_c.Win32_BIOS()[0]
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394498(v=vs.85).aspx
timeinfo = wmi_c.Win32_TimeZone()[0]
# https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-computersystemproduct
csproductinfo = wmi_c.Win32_ComputerSystemProduct()[0]
try:
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394102%28v=vs.85%29.aspx
systeminfo = wmi_c.Win32_ComputerSystem()[0]
grains.update(
{
"manufacturer": _clean_value(
"manufacturer", systeminfo.Manufacturer
),
"productname": _clean_value("productname", systeminfo.Model),
}
)
except IndexError:
grains.update({"manufacturer": None, "productname": None})
log.warning("Computer System info not available on this system")
try:
# https://msdn.microsoft.com/en-us/library/aa394239(v=vs.85).aspx
osinfo = wmi_c.Win32_OperatingSystem()[0]
os_release = _windows_os_release_grain(
caption=osinfo.Caption, product_type=osinfo.ProductType
)
grains.update(
{
"kernelrelease": _clean_value("kernelrelease", osinfo.Version),
"osfullname": _clean_value("osfullname", osinfo.Caption),
"osmanufacturer": _clean_value(
"osmanufacturer", osinfo.Manufacturer
),
"osrelease": _clean_value("osrelease", os_release),
"osversion": _clean_value("osversion", osinfo.Version),
}
)
except IndexError:
grains.update(
{
"kernelrelease": None,
"osfullname": None,
"osmanufacturer": None,
"osrelease": None,
"osversion": None,
}
)
log.warning("Operating System info not available on this system")
try:
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394077(v=vs.85).aspx
biosinfo = wmi_c.Win32_BIOS()[0]
grains.update(
{
# bios name had a bunch of whitespace appended to it in my testing
# 'PhoenixBIOS 4.0 Release 6.0 '
"biosversion": _clean_value("biosversion", biosinfo.Name.strip()),
"biosstring": _clean_value("string", biosinfo.Version),
"serialnumber": _clean_value("serialnumber", biosinfo.SerialNumber),
}
)
except IndexError:
grains.update(
{"biosstring": None, "biosversion": None, "serialnumber": None}
)
log.warning("BIOS info not available on this system")
try:
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394498(v=vs.85).aspx
timeinfo = wmi_c.Win32_TimeZone()[0]
grains.update(
{
"timezone": _clean_value("timezone", timeinfo.Description),
}
)
except IndexError:
grains.update({"timezone": None})
log.warning("TimeZone info not available on this system")
try:
# https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-computersystemproduct
csproductinfo = wmi_c.Win32_ComputerSystemProduct()[0]
grains.update(
{
"uuid": _clean_value("uuid", csproductinfo.UUID.lower()),
}
)
except IndexError:
grains.update({"uuid": None})
log.warning("Computer System Product info not available on this system")
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa394072(v=vs.85).aspx
motherboard = {"product": None, "serial": None}
try:
motherboardinfo = wmi_c.Win32_BaseBoard()[0]
motherboard["product"] = motherboardinfo.Product
motherboard["serial"] = motherboardinfo.SerialNumber
grains.update(
{
"motherboard": {
"productname": _clean_value(
"motherboard.productname", motherboardinfo.Product
),
"serialnumber": _clean_value(
"motherboard.serialnumber", motherboardinfo.SerialNumber
),
},
}
)
except IndexError:
grains.update(
{
"motherboard": {"productname": None, "serialnumber": None},
}
)
log.debug("Motherboard info not available on this system")
kernel_version = platform.version()
info = salt.utils.win_osinfo.get_os_version_info()
grains.update(
{
"kernelversion": _clean_value("kernelversion", platform.version()),
}
)
net_info = salt.utils.win_osinfo.get_join_info()
service_pack = None
if info["ServicePackMajor"] > 0:
service_pack = "".join(["SP", str(info["ServicePackMajor"])])
os_release = _windows_os_release_grain(
caption=osinfo.Caption, product_type=osinfo.ProductType
grains.update(
{
"windowsdomain": _clean_value("windowsdomain", net_info["Domain"]),
"windowsdomaintype": _clean_value(
"windowsdomaintype", net_info["DomainType"]
),
}
)
grains = {
"kernelrelease": _clean_value("kernelrelease", osinfo.Version),
"kernelversion": _clean_value("kernelversion", kernel_version),
"osversion": _clean_value("osversion", osinfo.Version),
"osrelease": _clean_value("osrelease", os_release),
"osservicepack": _clean_value("osservicepack", service_pack),
"osmanufacturer": _clean_value("osmanufacturer", osinfo.Manufacturer),
"manufacturer": _clean_value("manufacturer", systeminfo.Manufacturer),
"productname": _clean_value("productname", systeminfo.Model),
# bios name had a bunch of whitespace appended to it in my testing
# 'PhoenixBIOS 4.0 Release 6.0 '
"biosversion": _clean_value("biosversion", biosinfo.Name.strip()),
"serialnumber": _clean_value("serialnumber", biosinfo.SerialNumber),
"osfullname": _clean_value("osfullname", osinfo.Caption),
"timezone": _clean_value("timezone", timeinfo.Description),
"uuid": _clean_value("uuid", csproductinfo.UUID.lower()),
"windowsdomain": _clean_value("windowsdomain", net_info["Domain"]),
"windowsdomaintype": _clean_value(
"windowsdomaintype", net_info["DomainType"]
),
"motherboard": {
"productname": _clean_value(
"motherboard.productname", motherboard["product"]
),
"serialnumber": _clean_value(
"motherboard.serialnumber", motherboard["serial"]
),
},
}
# test for virtualized environments
# I only had VMware available so the rest are unvalidated
if "VRTUAL" in biosinfo.Version: # (not a typo)
grains["virtual"] = "HyperV"
elif "A M I" in biosinfo.Version:
grains["virtual"] = "VirtualPC"
elif "VMware" in systeminfo.Model:
grains["virtual"] = "VMware"
elif "VirtualBox" in systeminfo.Model:
grains["virtual"] = "VirtualBox"
elif "Xen" in biosinfo.Version:
grains["virtual"] = "Xen"
if "HVM domU" in systeminfo.Model:
grains["virtual_subtype"] = "HVM domU"
elif "OpenStack" in systeminfo.Model:
grains["virtual"] = "OpenStack"
elif "AMAZON" in biosinfo.Version:
grains["virtual"] = "EC2"
info = salt.utils.win_osinfo.get_os_version_info()
if info["ServicePackMajor"] > 0:
service_pack = "".join(["SP", str(info["ServicePackMajor"])])
grains.update(
{
"osservicepack": _clean_value("osservicepack", service_pack),
}
)
else:
grains.update({"osservicepack": None})
return grains

View file

@ -2868,6 +2868,10 @@ def test_virtual_has_virtual_grain():
assert virtual_grains["virtual"] != "physical"
def test__windows_platform_data():
pass
@pytest.mark.skip_unless_on_windows
@pytest.mark.parametrize(
("osdata", "expected"),
@ -2875,6 +2879,13 @@ def test_virtual_has_virtual_grain():
({"kernel": "Not Windows"}, {}),
({"kernel": "Windows"}, {"virtual": "physical"}),
({"kernel": "Windows", "manufacturer": "QEMU"}, {"virtual": "kvm"}),
({"kernel": "Windows", "biosstring": "VRTUAL"}, {"virtual": "HyperV"}),
({"kernel": "Windows", "biosstring": "A M I"}, {"virtual": "VirtualPC"}),
(
{"kernel": "Windows", "biosstring": "Xen", "productname": "HVM domU"},
{"virtual": "Xen", "virtual_subtype": "HVM domU"},
),
({"kernel": "Windows", "biosstring": "AMAZON"}, {"virtual": "EC2"}),
({"kernel": "Windows", "manufacturer": "Bochs"}, {"virtual": "kvm"}),
(
{"kernel": "Windows", "productname": "oVirt"},
@ -2884,10 +2895,6 @@ def test_virtual_has_virtual_grain():
{"kernel": "Windows", "productname": "RHEV Hypervisor"},
{"virtual": "kvm", "virtual_subtype": "rhev"},
),
(
{"kernel": "Windows", "productname": "CloudStack KVM Hypervisor"},
{"virtual": "kvm", "virtual_subtype": "cloudstack"},
),
(
{"kernel": "Windows", "productname": "VirtualBox"},
{"virtual": "VirtualBox"},
@ -2915,6 +2922,7 @@ def test_virtual_has_virtual_grain():
},
{"virtual": "VirtualPC"},
),
({"kernel": "Windows", "productname": "OpenStack"}, {"virtual": "OpenStack"}),
(
{"kernel": "Windows", "manufacturer": "Parallels Software"},
{"virtual": "Parallels"},
@ -2923,6 +2931,10 @@ def test_virtual_has_virtual_grain():
{"kernel": "Windows", "manufacturer": None, "productname": None},
{"virtual": "physical"},
),
(
{"kernel": "Windows", "productname": "CloudStack KVM Hypervisor"},
{"virtual": "kvm", "virtual_subtype": "cloudstack"},
),
],
)
def test__windows_virtual(osdata, expected):
@ -2943,17 +2955,7 @@ def test_windows_virtual_set_virtual_grain():
_,
) = platform.uname()
with patch.dict(
core.__salt__,
{
"cmd.run": salt.modules.cmdmod.run,
"cmd.run_all": salt.modules.cmdmod.run_all,
"cmd.retcode": salt.modules.cmdmod.retcode,
"smbios.get": salt.modules.smbios.get,
},
):
virtual_grains = core._windows_virtual(osdata)
virtual_grains = core._windows_virtual(osdata)
assert "virtual" in virtual_grains
@ -2971,46 +2973,15 @@ def test_windows_virtual_has_virtual_grain():
_,
) = platform.uname()
with patch.dict(
core.__salt__,
{
"cmd.run": salt.modules.cmdmod.run,
"cmd.run_all": salt.modules.cmdmod.run_all,
"cmd.retcode": salt.modules.cmdmod.retcode,
"smbios.get": salt.modules.smbios.get,
},
):
virtual_grains = core._windows_virtual(osdata)
virtual_grains = core._windows_virtual(osdata)
assert "virtual" in virtual_grains
assert virtual_grains["virtual"] != "physical"
@pytest.mark.skip_unless_on_windows
def test_osdata_virtual_key_win():
with patch.dict(
core.__salt__,
{
"cmd.run": salt.modules.cmdmod.run,
"cmd.run_all": salt.modules.cmdmod.run_all,
"cmd.retcode": salt.modules.cmdmod.retcode,
"smbios.get": salt.modules.smbios.get,
},
):
_windows_platform_data_ret = core.os_data()
_windows_platform_data_ret["virtual"] = "something"
with patch.object(
core, "_windows_platform_data", return_value=_windows_platform_data_ret
) as _windows_platform_data:
osdata_grains = core.os_data()
_windows_platform_data.assert_called_once()
assert "virtual" in osdata_grains
assert osdata_grains["virtual"] != "physical"
osdata_grains = core.os_data()
assert "virtual" in osdata_grains
@pytest.mark.skip_unless_on_linux

View file

@ -0,0 +1,223 @@
import pytest
import salt.grains.core as core
from tests.support.mock import MagicMock, Mock, patch
pytestmark = [
pytest.mark.skip_unless_on_windows,
]
wmi = pytest.importorskip("wmi", reason="WMI only available on Windows")
def test__windows_platform_data_index_errors():
# mock = [MagicMock(Manufacturer="Dell Inc.", Model="Precision 5820 Tower")]
# mock = [MagicMock(
# Version="10.0.22631",
# Caption="Microsoft Windows 11 Enterprise",
# Manufacturer="Microsoft Corporation",
# ProductType=1,
# )]
WMI = Mock()
platform_version = "1.2.3"
os_version_info = {"Domain": "test", "DomainType": "test_type"}
with (
patch("salt.utils.winapi.Com", MagicMock()),
patch.object(wmi, "WMI", Mock(return_value=WMI)),
patch.object(WMI, "Win32_ComputerSystem", return_value=[]),
patch.object(WMI, "Win32_OperatingSystem", return_value=[]),
patch.object(WMI, "Win32_BIOS", return_value=[]),
patch.object(WMI, "Win32_TimeZone", return_value=[]),
patch.object(WMI, "Win32_ComputerSystemProduct", return_value=[]),
patch.object(WMI, "Win32_BaseBoard", return_value=[]),
patch("platform.version", return_value=platform_version),
patch("salt.utils.win_osinfo.get_join_info", return_value=os_version_info),
):
result = core._windows_platform_data()
expected = {
"biosstring": None,
"biosversion": None,
"kernelrelease": None,
"kernelversion": platform_version,
"manufacturer": None,
"motherboard": {"productname": None, "serialnumber": None},
"osfullname": None,
"osmanufacturer": None,
"osrelease": None,
"osservicepack": None,
"osversion": None,
"productname": None,
"serialnumber": None,
"timezone": None,
"uuid": None,
"windowsdomain": os_version_info["Domain"],
"windowsdomaintype": os_version_info["DomainType"],
}
assert result == expected
def test__windows_platform_data_computer_system():
mock = [MagicMock(Manufacturer="Dell Inc.", Model="Precision 5820 Tower")]
WMI = Mock()
platform_version = "1.2.3"
os_version_info = {"Domain": "test", "DomainType": "test_type"}
with (
patch("salt.utils.winapi.Com", MagicMock()),
patch.object(wmi, "WMI", Mock(return_value=WMI)),
patch.object(WMI, "Win32_ComputerSystem", return_value=mock),
patch.object(WMI, "Win32_OperatingSystem", return_value=[]),
patch.object(WMI, "Win32_BIOS", return_value=[]),
patch.object(WMI, "Win32_TimeZone", return_value=[]),
patch.object(WMI, "Win32_ComputerSystemProduct", return_value=[]),
patch.object(WMI, "Win32_BaseBoard", return_value=[]),
patch("platform.version", return_value=platform_version),
patch("salt.utils.win_osinfo.get_join_info", return_value=os_version_info),
):
result = core._windows_platform_data()
assert result["manufacturer"] == "Dell Inc."
assert result["productname"] == "Precision 5820 Tower"
def test__windows_platform_data_operating_system():
mock = [
MagicMock(
Version="10.0.22631",
Caption="Microsoft Windows 11 Enterprise",
Manufacturer="Microsoft Corporation",
ProductType=1,
)
]
WMI = Mock()
platform_version = "1.2.3"
os_version_info = {"Domain": "test", "DomainType": "test_type"}
with (
patch("salt.utils.winapi.Com", MagicMock()),
patch.object(wmi, "WMI", Mock(return_value=WMI)),
patch.object(WMI, "Win32_ComputerSystem", return_value=[]),
patch.object(WMI, "Win32_OperatingSystem", return_value=mock),
patch.object(WMI, "Win32_BIOS", return_value=[]),
patch.object(WMI, "Win32_TimeZone", return_value=[]),
patch.object(WMI, "Win32_ComputerSystemProduct", return_value=[]),
patch.object(WMI, "Win32_BaseBoard", return_value=[]),
patch("platform.version", return_value=platform_version),
patch("salt.utils.win_osinfo.get_join_info", return_value=os_version_info),
):
result = core._windows_platform_data()
assert result["kernelrelease"] == "10.0.22631"
assert result["osfullname"] == "Microsoft Windows 11 Enterprise"
assert result["osmanufacturer"] == "Microsoft Corporation"
assert result["osrelease"] == "11"
assert result["osversion"] == "10.0.22631"
def test__windows_platform_data_bios():
mock = [
MagicMock(
Name="11.22.33",
Version="DELL - 1072009",
SerialNumber="BCF3H13",
)
]
WMI = Mock()
platform_version = "1.2.3"
os_version_info = {"Domain": "test", "DomainType": "test_type"}
with (
patch("salt.utils.winapi.Com", MagicMock()),
patch.object(wmi, "WMI", Mock(return_value=WMI)),
patch.object(WMI, "Win32_ComputerSystem", return_value=[]),
patch.object(WMI, "Win32_OperatingSystem", return_value=[]),
patch.object(WMI, "Win32_BIOS", return_value=mock),
patch.object(WMI, "Win32_TimeZone", return_value=[]),
patch.object(WMI, "Win32_ComputerSystemProduct", return_value=[]),
patch.object(WMI, "Win32_BaseBoard", return_value=[]),
patch("platform.version", return_value=platform_version),
patch("salt.utils.win_osinfo.get_join_info", return_value=os_version_info),
):
result = core._windows_platform_data()
assert result["biosversion"] == "11.22.33"
assert result["biosstring"] == "DELL - 1072009"
assert result["serialnumber"] == "BCF3H13"
def test__windows_platform_data_timezone():
mock = [
MagicMock(
Description="(UTC-07:00) Mountain Time (US & Canada)",
)
]
WMI = Mock()
platform_version = "1.2.3"
os_version_info = {"Domain": "test", "DomainType": "test_type"}
with (
patch("salt.utils.winapi.Com", MagicMock()),
patch.object(wmi, "WMI", Mock(return_value=WMI)),
patch.object(WMI, "Win32_ComputerSystem", return_value=[]),
patch.object(WMI, "Win32_OperatingSystem", return_value=[]),
patch.object(WMI, "Win32_BIOS", return_value=[]),
patch.object(WMI, "Win32_TimeZone", return_value=mock),
patch.object(WMI, "Win32_ComputerSystemProduct", return_value=[]),
patch.object(WMI, "Win32_BaseBoard", return_value=[]),
patch("platform.version", return_value=platform_version),
patch("salt.utils.win_osinfo.get_join_info", return_value=os_version_info),
):
result = core._windows_platform_data()
assert result["timezone"] == "(UTC-07:00) Mountain Time (US & Canada)"
def test__windows_platform_data_computer_system_product():
mock = [
MagicMock(
UUID="4C4C4544-0043-4610-8030-C2C04F483033",
)
]
WMI = Mock()
platform_version = "1.2.3"
os_version_info = {"Domain": "test", "DomainType": "test_type"}
with (
patch("salt.utils.winapi.Com", MagicMock()),
patch.object(wmi, "WMI", Mock(return_value=WMI)),
patch.object(WMI, "Win32_ComputerSystem", return_value=[]),
patch.object(WMI, "Win32_OperatingSystem", return_value=[]),
patch.object(WMI, "Win32_BIOS", return_value=[]),
patch.object(WMI, "Win32_TimeZone", return_value=[]),
patch.object(WMI, "Win32_ComputerSystemProduct", return_value=mock),
patch.object(WMI, "Win32_BaseBoard", return_value=[]),
patch("platform.version", return_value=platform_version),
patch("salt.utils.win_osinfo.get_join_info", return_value=os_version_info),
):
result = core._windows_platform_data()
assert result["uuid"] == "4c4c4544-0043-4610-8030-c2c04f483033"
def test__windows_platform_data_baseboard():
mock = [
MagicMock(
Product="002KVM",
SerialNumber="/BCF0H03/CNFCW0097F00TM/",
)
]
WMI = Mock()
platform_version = "1.2.3"
os_version_info = {"Domain": "test", "DomainType": "test_type"}
with (
patch("salt.utils.winapi.Com", MagicMock()),
patch.object(wmi, "WMI", Mock(return_value=WMI)),
patch.object(WMI, "Win32_ComputerSystem", return_value=[]),
patch.object(WMI, "Win32_OperatingSystem", return_value=[]),
patch.object(WMI, "Win32_BIOS", return_value=[]),
patch.object(WMI, "Win32_TimeZone", return_value=[]),
patch.object(WMI, "Win32_ComputerSystemProduct", return_value=[]),
patch.object(WMI, "Win32_BaseBoard", return_value=mock),
patch("platform.version", return_value=platform_version),
patch("salt.utils.win_osinfo.get_join_info", return_value=os_version_info),
):
result = core._windows_platform_data()
assert result["motherboard"]["productname"] == "002KVM"
assert result["motherboard"]["serialnumber"] == "/BCF0H03/CNFCW0097F00TM/"