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:
Christian Manivong
2026-06-24 11:23:24 +02:00
co-authored by Claude Sonnet 4.6
parent 8cc48ca383
commit e065515de0
2 changed files with 407 additions and 78 deletions
+168
View File
@@ -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