feat: VM/bare-metal detection in get_facts() — vendor, model, serial
_collect_platform_info() reads sys_vendor, product_name/version,
product_serial, product_uuid and systemd-detect-virt in one SSH
round-trip. Result:
- Bare-metal: vendor from DMI sys_vendor (e.g. "Dell Inc."), model
from product_name (product_version preferred when it looks like a
marketing name), serial from product_serial.
- VM (KVM/VMware/Hyper-V/Xen/VirtualBox): vendor is the hypervisor
name, model is "Virtual Machine", serial prefers product_serial and
falls back to product_uuid (VM UUID).
- Container (Docker/LXC/Podman): vendor is the container runtime,
model is "Container".
- Junk DMI values ("To Be Filled By O.E.M." etc.) are filtered.
- Falls back to VENDOR = "Linux" when DMI is completely unavailable.
13 new unit tests covering all scenarios including SSH failure and
detect-virt unavailability.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
8cc48ca383
commit
e065515de0
@@ -339,3 +339,171 @@ def test_apply_updates_unsupported_pm_raises(driver):
|
||||
driver._pkg_manager = "zypper"
|
||||
with pytest.raises(NotImplementedError):
|
||||
driver.apply_updates(["curl"])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _collect_platform_info
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _dmi_output(
|
||||
sys_vendor: str,
|
||||
product_name: str,
|
||||
product_version: str,
|
||||
product_serial: str,
|
||||
product_uuid: str,
|
||||
detect_virt: str,
|
||||
) -> str:
|
||||
return "\n".join([sys_vendor, product_name, product_version, product_serial, product_uuid, detect_virt])
|
||||
|
||||
|
||||
class TestCollectPlatformInfo:
|
||||
def test_baremetal_dell(self, driver):
|
||||
raw = _dmi_output(
|
||||
"Dell Inc.", "PowerEdge R720", "Not Specified", "ABC123",
|
||||
"8a2e3f00-dead-beef-0000-123456789abc", "none",
|
||||
)
|
||||
with patch.object(driver, "_send", return_value=raw):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["vendor"] == "Dell Inc."
|
||||
assert info["model"] == "PowerEdge R720"
|
||||
assert info["serial"] == "ABC123"
|
||||
assert info["is_vm"] is False
|
||||
|
||||
def test_baremetal_lenovo_product_version_preferred(self, driver):
|
||||
raw = _dmi_output(
|
||||
"LENOVO", "10M8000VUS", "ThinkCentre M910x", "MP1234",
|
||||
"8a2e3f00-dead-beef-0000-123456789abc", "none",
|
||||
)
|
||||
with patch.object(driver, "_send", return_value=raw):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["vendor"] == "LENOVO"
|
||||
assert info["model"] == "ThinkCentre M910x"
|
||||
assert info["serial"] == "MP1234"
|
||||
assert info["is_vm"] is False
|
||||
|
||||
def test_vm_kvm(self, driver):
|
||||
raw = _dmi_output(
|
||||
"QEMU", "Standard PC (i440FX + PIIX, 1996)", "pc-i440fx-9.1", "",
|
||||
"4c4c4544-0000-2010-8020-b4c04f534a31", "kvm",
|
||||
)
|
||||
with patch.object(driver, "_send", return_value=raw):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["vendor"] == "KVM"
|
||||
assert info["model"] == "Virtual Machine"
|
||||
assert info["serial"] == "4c4c4544-0000-2010-8020-b4c04f534a31"
|
||||
assert info["is_vm"] is True
|
||||
|
||||
def test_vm_vmware(self, driver):
|
||||
raw = _dmi_output(
|
||||
"VMware, Inc.", "VMware Virtual Platform", "None", "VMware-42 12 34 56",
|
||||
"4244560c-dead-beef-0000-abcdef123456", "vmware",
|
||||
)
|
||||
with patch.object(driver, "_send", return_value=raw):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["vendor"] == "VMware ESXi"
|
||||
assert info["model"] == "Virtual Machine"
|
||||
assert info["serial"] == "VMware-42 12 34 56"
|
||||
assert info["is_vm"] is True
|
||||
|
||||
def test_vm_hyperv(self, driver):
|
||||
raw = _dmi_output(
|
||||
"Microsoft Corporation", "Virtual Machine", "Hyper-V UEFI Release v4.1", "",
|
||||
"7C5B4B1F-1234-5678-ABCD-000000000001", "microsoft",
|
||||
)
|
||||
with patch.object(driver, "_send", return_value=raw):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["vendor"] == "Microsoft Hyper-V"
|
||||
assert info["model"] == "Virtual Machine"
|
||||
assert info["serial"] == "7C5B4B1F-1234-5678-ABCD-000000000001"
|
||||
assert info["is_vm"] is True
|
||||
|
||||
def test_junk_dmi_values_filtered(self, driver):
|
||||
raw = _dmi_output(
|
||||
"To Be Filled By O.E.M.", "To Be Filled By O.E.M.", "Not Specified",
|
||||
"To Be Filled By O.E.M.", "", "none",
|
||||
)
|
||||
with patch.object(driver, "_send", return_value=raw):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["vendor"] == ""
|
||||
assert info["model"] == ""
|
||||
assert info["is_vm"] is False
|
||||
|
||||
def test_vm_kvm_fallback_via_dmi_when_detect_virt_unavailable(self, driver):
|
||||
# systemd-detect-virt returns "none" (not installed), sys_vendor reveals QEMU
|
||||
raw = _dmi_output(
|
||||
"QEMU", "Standard PC (i440FX + PIIX, 1996)", "", "",
|
||||
"4c4c4544-0000-2010-8020-b4c04f534a31", "none",
|
||||
)
|
||||
with patch.object(driver, "_send", return_value=raw):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["is_vm"] is True
|
||||
assert info["vendor"] == "KVM"
|
||||
assert info["model"] == "Virtual Machine"
|
||||
|
||||
def test_container_docker(self, driver):
|
||||
raw = _dmi_output(
|
||||
"QEMU", "Standard PC (i440FX + PIIX, 1996)", "", "",
|
||||
"4c4c4544-0000-2010-8020-b4c04f534a31", "docker",
|
||||
)
|
||||
with patch.object(driver, "_send", return_value=raw):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["vendor"] == "Docker"
|
||||
assert info["model"] == "Container"
|
||||
assert info["is_vm"] is True
|
||||
|
||||
def test_container_lxc(self, driver):
|
||||
raw = _dmi_output("", "", "", "", "", "lxc")
|
||||
with patch.object(driver, "_send", return_value=raw):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["vendor"] == "LXC"
|
||||
assert info["model"] == "Container"
|
||||
assert info["is_vm"] is True
|
||||
|
||||
def test_ssh_failure_returns_safe_defaults(self, driver):
|
||||
with patch.object(driver, "_send", side_effect=Exception("SSH error")):
|
||||
info = driver._collect_platform_info()
|
||||
assert info["vendor"] == ""
|
||||
assert info["model"] == ""
|
||||
assert info["is_vm"] is False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_facts uses _collect_platform_info
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_get_facts_baremetal_vendor_model_serial(driver):
|
||||
platform = {"vendor": "Dell Inc.", "model": "PowerEdge R720", "serial": "ABC123", "is_vm": False}
|
||||
with patch.object(driver, "_collect_platform_info", return_value=platform), \
|
||||
patch.object(driver, "_parse_uptime", return_value=86400), \
|
||||
patch.object(driver, "_send", side_effect=["myhost", "myhost.example.com", "Debian GNU/Linux 12", "eth0\neth1"]):
|
||||
facts = driver.get_facts()
|
||||
assert facts["vendor"] == "Dell Inc."
|
||||
assert facts["model"] == "PowerEdge R720"
|
||||
assert facts["serial_number"] == "ABC123"
|
||||
assert facts["hostname"] == "myhost"
|
||||
assert facts["uptime"] == 86400
|
||||
|
||||
|
||||
def test_get_facts_vm_kvm(driver):
|
||||
platform = {
|
||||
"vendor": "KVM", "model": "Virtual Machine",
|
||||
"serial": "4c4c4544-0000-2010-8020-b4c04f534a31", "is_vm": True,
|
||||
}
|
||||
with patch.object(driver, "_collect_platform_info", return_value=platform), \
|
||||
patch.object(driver, "_parse_uptime", return_value=3600), \
|
||||
patch.object(driver, "_send", side_effect=["vmhost", "vmhost.local", "Ubuntu 22.04 LTS", "eth0"]):
|
||||
facts = driver.get_facts()
|
||||
assert facts["vendor"] == "KVM"
|
||||
assert facts["model"] == "Virtual Machine"
|
||||
assert facts["serial_number"] == "4c4c4544-0000-2010-8020-b4c04f534a31"
|
||||
|
||||
|
||||
def test_get_facts_fallback_vendor_when_dmi_empty(driver):
|
||||
platform = {"vendor": "", "model": "", "serial": "", "is_vm": False}
|
||||
with patch.object(driver, "_collect_platform_info", return_value=platform), \
|
||||
patch.object(driver, "_parse_uptime", return_value=0), \
|
||||
patch.object(driver, "_send", side_effect=["host", "host.local", "Alpine Linux 3.19", "eth0"]):
|
||||
facts = driver.get_facts()
|
||||
assert facts["vendor"] == "Linux" # fallback to VENDOR class attribute
|
||||
|
||||
Reference in New Issue
Block a user