mirror of
https://github.com/nicolargo/glances.git
synced 2026-07-31 07:16:17 -04:00
172 lines
4.9 KiB
Python
Executable File
172 lines
4.9 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# Glances - An eye on your system
|
|
#
|
|
# SPDX-FileCopyrightText: 2026 Nicolas Hennion <nicolas@nicolargo.com>
|
|
#
|
|
# SPDX-License-Identifier: LGPL-3.0-only
|
|
#
|
|
|
|
"""Tests for the v5 raid plugin model."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from glances.config_v5 import GlancesConfigV5
|
|
from glances.plugins.raid import model_v5 as raid_mod
|
|
from glances.plugins.raid.model_v5 import PluginModel
|
|
from glances.stats_store_v5 import StatsStoreV5
|
|
|
|
|
|
@pytest.fixture
|
|
def store() -> StatsStoreV5:
|
|
return StatsStoreV5()
|
|
|
|
|
|
@pytest.fixture
|
|
def config(tmp_path, monkeypatch) -> GlancesConfigV5:
|
|
monkeypatch.setattr(GlancesConfigV5, "SYSTEM_CONFIG_PATH", tmp_path / "etc" / "glances.conf")
|
|
return GlancesConfigV5()
|
|
|
|
|
|
def test_plugin_identity(store, config):
|
|
p = PluginModel(store, config)
|
|
assert p.plugin_name == "raid"
|
|
assert p.IS_COLLECTION is True
|
|
assert p._primary_key == "name"
|
|
|
|
|
|
def test_fields_description_flags():
|
|
fd = PluginModel.fields_description
|
|
assert fd["name"]["primary_key"] is True
|
|
# RAID level / status / components / config are internal metadata.
|
|
for key in ("type", "status", "components", "config"):
|
|
assert fd[key].get("internal") is True
|
|
assert fd[key].get("watched", False) is False
|
|
# Disk counters are exportable but not watched (levels are bespoke).
|
|
for key in ("used", "available"):
|
|
assert fd[key].get("internal", False) is False
|
|
assert fd[key].get("watched", False) is False
|
|
|
|
|
|
def test_emits_alerts_true():
|
|
# Deliberate divergence from v4 (colour-only): degraded/inactive RAID
|
|
# is a real incident and must feed the alert pipeline.
|
|
assert PluginModel.EMITS_ALERTS is True
|
|
|
|
|
|
_MD0 = {
|
|
"type": "raid1",
|
|
"status": "active",
|
|
"used": 2,
|
|
"available": 2,
|
|
"components": {"sda1": "0", "sdb1": "1"},
|
|
"config": "UU",
|
|
}
|
|
_MD1 = {
|
|
"type": "raid5",
|
|
"status": "active",
|
|
"used": 3,
|
|
"available": 4,
|
|
"components": {"sdc1": "0", "sdd1": "1", "sde1": "2"},
|
|
"config": "UUU_",
|
|
}
|
|
|
|
|
|
class _FakeMdStat:
|
|
"""Stand-in for pymdstat.MdStat — returns a fixed name-keyed arrays dict."""
|
|
|
|
_arrays: dict = {}
|
|
|
|
def get_stats(self):
|
|
return {"arrays": self._arrays}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_grab_flattens_and_injects_name(store, config, monkeypatch):
|
|
fake = type("F", (_FakeMdStat,), {"_arrays": {"md0": dict(_MD0), "md1": dict(_MD1)}})
|
|
monkeypatch.setattr(raid_mod, "MdStat", fake)
|
|
p = PluginModel(store, config)
|
|
rows = await p._grab_stats()
|
|
by_name = {r["name"]: r for r in rows}
|
|
assert set(by_name) == {"md0", "md1"}
|
|
assert by_name["md0"]["type"] == "raid1"
|
|
assert by_name["md0"]["used"] == 2
|
|
assert by_name["md1"]["available"] == 4
|
|
# The original array dict must carry a `name` key now (primary key).
|
|
assert by_name["md0"]["name"] == "md0"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_grab_import_error_returns_empty(store, config, monkeypatch):
|
|
monkeypatch.setattr(raid_mod, "MdStat", None)
|
|
p = PluginModel(store, config)
|
|
assert await p._grab_stats() == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_grab_runtime_failure_returns_empty(store, config, monkeypatch):
|
|
class _Boom(_FakeMdStat):
|
|
def get_stats(self):
|
|
raise OSError("cannot read /proc/mdstat")
|
|
|
|
monkeypatch.setattr(raid_mod, "MdStat", _Boom)
|
|
p = PluginModel(store, config)
|
|
assert await p._grab_stats() == []
|
|
|
|
|
|
def _levels(p, rows):
|
|
p._stats = rows
|
|
p._derived_parameters()
|
|
return p._levels
|
|
|
|
|
|
def _array(name, type_, status, used, available):
|
|
return {
|
|
"name": name,
|
|
"type": type_,
|
|
"status": status,
|
|
"used": used,
|
|
"available": available,
|
|
"components": {},
|
|
"config": "",
|
|
}
|
|
|
|
|
|
def test_level_raid0_is_ok(store, config):
|
|
# raid0 has no redundancy; v4 raid_alert short-circuits to OK.
|
|
p = PluginModel(store, config)
|
|
lv = _levels(p, [_array("md0", "raid0", "active", 2, 2)])
|
|
assert lv["md0"]["status"]["level"] == "ok"
|
|
|
|
|
|
def test_level_inactive_is_critical(store, config):
|
|
p = PluginModel(store, config)
|
|
lv = _levels(p, [_array("md0", "raid1", "inactive", 2, 2)])
|
|
assert lv["md0"]["status"]["level"] == "critical"
|
|
|
|
|
|
def test_level_degraded_is_warning(store, config):
|
|
p = PluginModel(store, config)
|
|
lv = _levels(p, [_array("md0", "raid5", "active", 3, 4)])
|
|
assert lv["md0"]["status"]["level"] == "warning"
|
|
|
|
|
|
def test_level_healthy_is_ok(store, config):
|
|
p = PluginModel(store, config)
|
|
lv = _levels(p, [_array("md0", "raid1", "active", 2, 2)])
|
|
assert lv["md0"]["status"]["level"] == "ok"
|
|
|
|
|
|
def test_level_none_when_counts_missing(store, config):
|
|
p = PluginModel(store, config)
|
|
lv = _levels(p, [_array("md0", "raid1", "active", None, 2)])
|
|
assert "md0" not in lv # no threshold source -> DEFAULT (no entry)
|
|
|
|
|
|
def test_level_prominent_false(store, config):
|
|
p = PluginModel(store, config)
|
|
lv = _levels(p, [_array("md0", "raid1", "inactive", 2, 2)])
|
|
assert lv["md0"]["status"]["prominent"] is False
|