mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-20 18:55:40 +00:00
698 lines
22 KiB
Go
698 lines
22 KiB
Go
package unifiedresources
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/mockruntime"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/storagehealth"
|
|
)
|
|
|
|
func TestResourceFromProxmoxNodeIncludesTemperature(t *testing.T) {
|
|
node := models.Node{
|
|
ID: "mock-cluster-pve1",
|
|
Name: "pve1",
|
|
Status: "online",
|
|
Temperature: &models.Temperature{
|
|
Available: true,
|
|
HasCPU: true,
|
|
CPUPackage: 55.2,
|
|
CPUMax: 61.9,
|
|
},
|
|
}
|
|
|
|
resource, _ := resourceFromProxmoxNode(node, nil)
|
|
if resource.Proxmox == nil {
|
|
t.Fatal("expected proxmox payload")
|
|
}
|
|
if resource.Proxmox.SourceID != node.ID {
|
|
t.Fatalf("SourceID = %q, want %q (must preserve legacy node ID for MetricsHistory)", resource.Proxmox.SourceID, node.ID)
|
|
}
|
|
if resource.Proxmox.Temperature == nil {
|
|
t.Fatal("expected proxmox temperature to be populated")
|
|
}
|
|
if got, want := *resource.Proxmox.Temperature, 61.9; got != want {
|
|
t.Fatalf("temperature = %v, want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromProxmoxNodeStoresEndpointIPAsIPAddress(t *testing.T) {
|
|
node := models.Node{
|
|
ID: "mock-cluster-minipc",
|
|
Name: "minipc",
|
|
Host: "https://10.0.0.5:8006",
|
|
}
|
|
|
|
_, identity := resourceFromProxmoxNode(node, nil)
|
|
if len(identity.Hostnames) != 1 || identity.Hostnames[0] != "minipc" {
|
|
t.Fatalf("Hostnames = %v, want [minipc]", identity.Hostnames)
|
|
}
|
|
if len(identity.IPAddresses) != 1 || identity.IPAddresses[0] != "10.0.0.5" {
|
|
t.Fatalf("IPAddresses = %v, want [10.0.0.5]", identity.IPAddresses)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromProxmoxNodeInheritsLinkedHostIdentity(t *testing.T) {
|
|
node := models.Node{
|
|
ID: "mock-cluster-minipc",
|
|
Name: "minipc",
|
|
Host: "https://10.0.0.5:8006",
|
|
LinkedAgentID: "host-1",
|
|
}
|
|
host := &models.Host{
|
|
ID: "host-1",
|
|
Hostname: "minipc.local",
|
|
MachineID: "machine-1",
|
|
ReportIP: "10.0.0.5",
|
|
NetworkInterfaces: []models.HostNetworkInterface{
|
|
{Name: "eth0", MAC: "00:11:22:33:44:55", Addresses: []string{"10.0.0.5/24"}},
|
|
},
|
|
}
|
|
|
|
_, identity := resourceFromProxmoxNode(node, host)
|
|
if identity.MachineID != "machine-1" {
|
|
t.Fatalf("MachineID = %q, want machine-1", identity.MachineID)
|
|
}
|
|
if len(identity.MACAddresses) != 1 || identity.MACAddresses[0] != "00:11:22:33:44:55" {
|
|
t.Fatalf("MACAddresses = %v, want [00:11:22:33:44:55]", identity.MACAddresses)
|
|
}
|
|
}
|
|
|
|
func TestMaxNodeTempHandlesUnavailableData(t *testing.T) {
|
|
if temp := maxNodeTemp(nil); temp != nil {
|
|
t.Fatalf("expected nil temperature for nil input, got %v", *temp)
|
|
}
|
|
|
|
input := &models.Temperature{
|
|
Available: false,
|
|
HasCPU: false,
|
|
}
|
|
if temp := maxNodeTemp(input); temp != nil {
|
|
t.Fatalf("expected nil temperature for unavailable snapshot, got %v", *temp)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromDockerHostIncludesTemperature(t *testing.T) {
|
|
value := 52.7
|
|
host := models.DockerHost{
|
|
ID: "docker-1",
|
|
Hostname: "docker-1",
|
|
DisplayName: "Docker 1",
|
|
Status: "online",
|
|
Temperature: &value,
|
|
}
|
|
|
|
resource, _ := resourceFromDockerHost(host)
|
|
if resource.Docker == nil {
|
|
t.Fatal("expected docker payload")
|
|
}
|
|
if resource.Docker.Temperature == nil {
|
|
t.Fatal("expected docker temperature to be populated")
|
|
}
|
|
if got, want := *resource.Docker.Temperature, value; got != want {
|
|
t.Fatalf("temperature = %v, want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromDockerHostOmitsStandaloneInactiveSwarm(t *testing.T) {
|
|
host := models.DockerHost{
|
|
ID: "docker-1",
|
|
Hostname: "docker-1",
|
|
Status: "online",
|
|
Swarm: &models.DockerSwarmInfo{
|
|
NodeRole: "worker",
|
|
LocalState: "inactive",
|
|
Scope: "node",
|
|
},
|
|
}
|
|
|
|
resource, _ := resourceFromDockerHost(host)
|
|
if resource.Docker == nil {
|
|
t.Fatal("expected docker payload")
|
|
}
|
|
if resource.Docker.Swarm != nil {
|
|
t.Fatalf("standalone inactive Docker Swarm metadata should be omitted, got %+v", resource.Docker.Swarm)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromDockerContainerIncludesContainerID(t *testing.T) {
|
|
container := models.DockerContainer{
|
|
ID: "aurora-3-abcdef123456",
|
|
Name: "web",
|
|
State: "running",
|
|
Image: "ghcr.io/example/web:1.0.0",
|
|
UptimeSeconds: 1234,
|
|
CPUPercent: 12.5,
|
|
}
|
|
|
|
host := models.DockerHost{
|
|
ID: "docker-1",
|
|
Hostname: "docker-1",
|
|
Runtime: "podman",
|
|
}
|
|
resource, _ := resourceFromDockerContainer(container, host)
|
|
if resource.Docker == nil {
|
|
t.Fatal("expected docker payload")
|
|
}
|
|
if got, want := resource.Docker.ContainerID, container.ID; got != want {
|
|
t.Fatalf("containerId = %q, want %q", got, want)
|
|
}
|
|
if got, want := resource.Docker.Runtime, host.Runtime; got != want {
|
|
t.Fatalf("runtime = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromHostProjectsAgentHostProfile(t *testing.T) {
|
|
host := models.Host{
|
|
ID: "tower-host",
|
|
Hostname: "tower",
|
|
Platform: "slackware",
|
|
OSName: "Unraid",
|
|
OSVersion: "7.1.0",
|
|
Status: "online",
|
|
}
|
|
|
|
resource, _ := resourceFromHost(host)
|
|
if resource.Technology != "linux" {
|
|
t.Fatalf("Technology = %q, want linux runtime platform", resource.Technology)
|
|
}
|
|
if resource.Agent == nil {
|
|
t.Fatal("expected agent payload")
|
|
}
|
|
if resource.Agent.Platform != "linux" {
|
|
t.Fatalf("agent platform = %q, want linux runtime platform", resource.Agent.Platform)
|
|
}
|
|
if resource.Agent.HostProfile != "unraid" {
|
|
t.Fatalf("agent host profile = %q, want unraid", resource.Agent.HostProfile)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromVMPreservesProxmoxPool(t *testing.T) {
|
|
vm := models.VM{
|
|
ID: "cluster-a:pve-a:101",
|
|
Name: "app-vm",
|
|
Node: "pve-a",
|
|
Pool: "prod-vms",
|
|
Instance: "cluster-a",
|
|
VMID: 101,
|
|
Status: "running",
|
|
}
|
|
|
|
resource, _ := resourceFromVM(vm)
|
|
if resource.Proxmox == nil {
|
|
t.Fatal("expected proxmox payload")
|
|
}
|
|
if got, want := resource.Proxmox.Pool, "prod-vms"; got != want {
|
|
t.Fatalf("pool = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromContainerPreservesProxmoxPool(t *testing.T) {
|
|
container := models.Container{
|
|
ID: "cluster-a:pve-a:202",
|
|
Name: "cache-ct",
|
|
Node: "pve-a",
|
|
Pool: "ops-lxc",
|
|
Instance: "cluster-a",
|
|
VMID: 202,
|
|
Status: "running",
|
|
Type: "lxc",
|
|
}
|
|
|
|
resource, _ := resourceFromContainer(container)
|
|
if resource.Proxmox == nil {
|
|
t.Fatal("expected proxmox payload")
|
|
}
|
|
if got, want := resource.Proxmox.Pool, "ops-lxc"; got != want {
|
|
t.Fatalf("pool = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromStorageIncludesStorageMetadata(t *testing.T) {
|
|
storage := models.Storage{
|
|
ID: "storage-1",
|
|
Name: "ceph-rbd-pool",
|
|
Node: "pve-1",
|
|
Instance: "cluster-a",
|
|
Type: "RBD",
|
|
Pool: "ceph/rbd-a",
|
|
Content: "images, rootdir,images",
|
|
Shared: true,
|
|
Status: "available",
|
|
Enabled: true,
|
|
Active: true,
|
|
Total: 100,
|
|
Used: 20,
|
|
Free: 80,
|
|
Usage: 20,
|
|
}
|
|
|
|
resource, _ := resourceFromStorage(storage)
|
|
if resource.Storage == nil {
|
|
t.Fatal("expected storage metadata payload")
|
|
}
|
|
if got, want := resource.Storage.Type, "rbd"; got != want {
|
|
t.Fatalf("storage type = %q, want %q", got, want)
|
|
}
|
|
if got, want := resource.Storage.Content, "images, rootdir,images"; got != want {
|
|
t.Fatalf("storage content = %q, want %q", got, want)
|
|
}
|
|
if got, want := resource.Storage.Pool, "ceph/rbd-a"; got != want {
|
|
t.Fatalf("storage pool = %q, want %q", got, want)
|
|
}
|
|
wantContentTypes := []string{"images", "rootdir"}
|
|
if len(resource.Storage.ContentTypes) != len(wantContentTypes) {
|
|
t.Fatalf("contentTypes length = %d, want %d (%v)", len(resource.Storage.ContentTypes), len(wantContentTypes), resource.Storage.ContentTypes)
|
|
}
|
|
for i, want := range wantContentTypes {
|
|
if got := resource.Storage.ContentTypes[i]; got != want {
|
|
t.Fatalf("contentTypes[%d] = %q, want %q", i, got, want)
|
|
}
|
|
}
|
|
if !resource.Storage.Shared {
|
|
t.Fatalf("expected shared=true")
|
|
}
|
|
if !resource.Storage.IsCeph {
|
|
t.Fatalf("expected isCeph=true")
|
|
}
|
|
if resource.Storage.IsZFS {
|
|
t.Fatalf("expected isZfs=false")
|
|
}
|
|
if resource.Proxmox == nil || resource.Proxmox.NodeName != "pve-1" || resource.Proxmox.Instance != "cluster-a" {
|
|
t.Fatalf("expected existing proxmox mapping to remain populated, got %+v", resource.Proxmox)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromStorageDetectsZFSMetadata(t *testing.T) {
|
|
storage := models.Storage{
|
|
ID: "storage-zfs",
|
|
Name: "local-zfs",
|
|
Node: "pve-2",
|
|
Instance: "cluster-a",
|
|
Type: "zfspool",
|
|
Content: "images",
|
|
Shared: false,
|
|
Status: "available",
|
|
Enabled: true,
|
|
Active: true,
|
|
ZFSPool: &models.ZFSPool{
|
|
Name: "rpool",
|
|
State: "ONLINE",
|
|
},
|
|
}
|
|
|
|
resource, _ := resourceFromStorage(storage)
|
|
if resource.Storage == nil {
|
|
t.Fatal("expected storage metadata payload")
|
|
}
|
|
if !resource.Storage.IsZFS {
|
|
t.Fatalf("expected isZfs=true")
|
|
}
|
|
if resource.Storage.IsCeph {
|
|
t.Fatalf("expected isCeph=false")
|
|
}
|
|
if resource.Storage.Shared {
|
|
t.Fatalf("expected shared=false")
|
|
}
|
|
}
|
|
|
|
func TestResourceFromStorageDerivesZFSTopologyRisk(t *testing.T) {
|
|
storage := models.Storage{
|
|
ID: "storage-zfs-risk",
|
|
Name: "tank",
|
|
Node: "pve-2",
|
|
Instance: "cluster-a",
|
|
Type: "zfspool",
|
|
Content: "images",
|
|
Shared: false,
|
|
Status: "available",
|
|
Enabled: true,
|
|
Active: true,
|
|
ZFSPool: &models.ZFSPool{
|
|
Name: "tank",
|
|
State: "ONLINE",
|
|
ChecksumErrors: 2,
|
|
},
|
|
}
|
|
|
|
resource, _ := resourceFromStorage(storage)
|
|
if resource.Status != StatusWarning {
|
|
t.Fatalf("Status = %q, want %q", resource.Status, StatusWarning)
|
|
}
|
|
if resource.Storage == nil || resource.Storage.Risk == nil {
|
|
t.Fatalf("expected zfs topology risk payload, got %+v", resource.Storage)
|
|
}
|
|
if resource.Storage.Risk.Level != storagehealth.RiskWarning {
|
|
t.Fatalf("risk level = %q, want %q", resource.Storage.Risk.Level, storagehealth.RiskWarning)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromPBSDatastoreDerivesRiskAndIncidents(t *testing.T) {
|
|
now := time.Now().UTC()
|
|
instance := models.PBSInstance{
|
|
ID: "pbs-1",
|
|
Name: "pbs-main",
|
|
Host: "https://pbs-main.local:8007",
|
|
LastSeen: now,
|
|
}
|
|
datastore := models.PBSDatastore{
|
|
Name: "backup-store",
|
|
Status: "online",
|
|
Total: 100,
|
|
Used: 96,
|
|
}
|
|
|
|
resource, _ := resourceFromPBSDatastore(instance, datastore)
|
|
if resource.Status != StatusWarning {
|
|
t.Fatalf("Status = %q, want %q", resource.Status, StatusWarning)
|
|
}
|
|
if resource.Storage == nil || resource.Storage.Risk == nil {
|
|
t.Fatalf("expected PBS datastore risk payload, got %+v", resource.Storage)
|
|
}
|
|
if resource.Storage.Risk.Level != storagehealth.RiskCritical {
|
|
t.Fatalf("risk level = %q, want %q", resource.Storage.Risk.Level, storagehealth.RiskCritical)
|
|
}
|
|
if len(resource.Incidents) != 1 {
|
|
t.Fatalf("expected 1 datastore incident, got %+v", resource.Incidents)
|
|
}
|
|
if resource.Incidents[0].Code != "capacity_runway_low" {
|
|
t.Fatalf("incident code = %q, want capacity_runway_low", resource.Incidents[0].Code)
|
|
}
|
|
if resource.Incidents[0].Source != "pbs" {
|
|
t.Fatalf("incident source = %q, want pbs", resource.Incidents[0].Source)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromPBSInstanceRollsUpDatastoreRisk(t *testing.T) {
|
|
now := time.Now().UTC()
|
|
instance := models.PBSInstance{
|
|
ID: "pbs-1",
|
|
Name: "pbs-main",
|
|
Host: "https://pbs-main.local:8007",
|
|
Status: "online",
|
|
LastSeen: now,
|
|
Datastores: []models.PBSDatastore{
|
|
{
|
|
Name: "backup-store",
|
|
Status: "online",
|
|
Total: 100,
|
|
Used: 96,
|
|
},
|
|
},
|
|
}
|
|
|
|
resource, _ := resourceFromPBSInstance(instance)
|
|
if resource.Status != StatusWarning {
|
|
t.Fatalf("Status = %q, want %q", resource.Status, StatusWarning)
|
|
}
|
|
if resource.PBS == nil || resource.PBS.StorageRisk == nil {
|
|
t.Fatalf("expected PBS storage risk payload, got %+v", resource.PBS)
|
|
}
|
|
if resource.PBS.StorageRisk.Level != storagehealth.RiskCritical {
|
|
t.Fatalf("storage risk level = %q, want %q", resource.PBS.StorageRisk.Level, storagehealth.RiskCritical)
|
|
}
|
|
if len(resource.Incidents) != 1 {
|
|
t.Fatalf("expected 1 rolled-up PBS incident, got %+v", resource.Incidents)
|
|
}
|
|
if resource.Incidents[0].Code != "capacity_runway_low" {
|
|
t.Fatalf("incident code = %q, want capacity_runway_low", resource.Incidents[0].Code)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromHostUnraidStorageIncludesTopologyMetadata(t *testing.T) {
|
|
host := models.Host{
|
|
ID: "tower-host",
|
|
Hostname: "tower",
|
|
DisplayName: "Tower",
|
|
Status: "online",
|
|
LastSeen: time.Now().UTC(),
|
|
Disks: []models.Disk{
|
|
{Mountpoint: "/mnt/user", Total: 1000, Used: 400, Free: 600, Usage: 40},
|
|
},
|
|
Unraid: &models.HostUnraidStorage{
|
|
ArrayStarted: true,
|
|
ArrayState: "STARTED",
|
|
SyncAction: "check",
|
|
SyncProgress: 22,
|
|
NumProtected: 1,
|
|
Disks: []models.HostUnraidDisk{
|
|
{Name: "parity", Role: "parity", Status: "online"},
|
|
{Name: "disk1", Role: "data", Status: "online"},
|
|
},
|
|
},
|
|
}
|
|
|
|
resource, identity := resourceFromHostUnraidStorage(host)
|
|
if resource.Type != ResourceTypeStorage {
|
|
t.Fatalf("Type = %q, want %q", resource.Type, ResourceTypeStorage)
|
|
}
|
|
if resource.Storage == nil {
|
|
t.Fatal("expected storage metadata payload")
|
|
}
|
|
if got := resource.Storage.Type; got != "unraid-array" {
|
|
t.Fatalf("storage type = %q, want unraid-array", got)
|
|
}
|
|
if got := resource.Storage.Platform; got != "unraid" {
|
|
t.Fatalf("platform = %q, want unraid", got)
|
|
}
|
|
if got := resource.Storage.Protection; got != "single-parity" {
|
|
t.Fatalf("protection = %q, want single-parity", got)
|
|
}
|
|
if resource.Metrics == nil || resource.Metrics.Disk == nil || resource.Metrics.Disk.Percent != 40 {
|
|
t.Fatalf("expected disk metrics from /mnt/user, got %+v", resource.Metrics)
|
|
}
|
|
if got := identity.MachineID; got != "" && !strings.Contains(got, "/storage/unraid-array") {
|
|
t.Fatalf("expected unraid storage identity suffix, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromHostDerivesStorageTopologyRisk(t *testing.T) {
|
|
host := models.Host{
|
|
ID: "tower-host",
|
|
Hostname: "tower",
|
|
Status: "online",
|
|
Unraid: &models.HostUnraidStorage{
|
|
ArrayStarted: true,
|
|
Disks: []models.HostUnraidDisk{
|
|
{Name: "parity", Role: "parity", Status: "disabled"},
|
|
{Name: "disk1", Role: "data", Status: "online"},
|
|
},
|
|
},
|
|
}
|
|
|
|
resource, _ := resourceFromHost(host)
|
|
if resource.Status != StatusWarning {
|
|
t.Fatalf("Status = %q, want %q", resource.Status, StatusWarning)
|
|
}
|
|
if resource.Agent == nil || resource.Agent.StorageRisk == nil {
|
|
t.Fatalf("expected host storage risk payload, got %+v", resource.Agent)
|
|
}
|
|
if resource.Agent.StorageRisk.Level != storagehealth.RiskCritical {
|
|
t.Fatalf("risk level = %q, want %q", resource.Agent.StorageRisk.Level, storagehealth.RiskCritical)
|
|
}
|
|
if resource.Agent.StorageRiskSummary != "Unraid parity disk parity is DISABLED" {
|
|
t.Fatalf("storage risk summary = %q", resource.Agent.StorageRiskSummary)
|
|
}
|
|
if resource.Agent.StoragePostureSummary != "Unraid parity disk parity is DISABLED" {
|
|
t.Fatalf("storage posture summary = %q", resource.Agent.StoragePostureSummary)
|
|
}
|
|
if !resource.Agent.ProtectionReduced || resource.Agent.ProtectionSummary != "Unraid parity disk parity is DISABLED" {
|
|
t.Fatalf("protection semantics = reduced:%v summary:%q", resource.Agent.ProtectionReduced, resource.Agent.ProtectionSummary)
|
|
}
|
|
if resource.Agent.Unraid == nil || resource.Agent.Unraid.Risk == nil {
|
|
t.Fatalf("expected unraid risk payload, got %+v", resource.Agent.Unraid)
|
|
}
|
|
if resource.Agent.Unraid.RiskSummary != "Unraid parity disk parity is DISABLED" || resource.Agent.Unraid.PostureSummary != "Unraid parity disk parity is DISABLED" {
|
|
t.Fatalf("unexpected unraid summaries %+v", resource.Agent.Unraid)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromHostSMARTDiskCarriesUnraidRole(t *testing.T) {
|
|
host := models.Host{
|
|
ID: "tower-host",
|
|
Hostname: "tower",
|
|
LastSeen: time.Now().UTC(),
|
|
Unraid: &models.HostUnraidStorage{
|
|
ArrayStarted: true,
|
|
Disks: []models.HostUnraidDisk{
|
|
{
|
|
Name: "parity",
|
|
Device: "/dev/sdb",
|
|
Role: "parity",
|
|
Status: "online",
|
|
Model: "Seagate",
|
|
Serial: "PARITY-1",
|
|
SizeBytes: 6_000_000_000_000,
|
|
SpunDown: true,
|
|
ReadCount: 11,
|
|
WriteCount: 12,
|
|
ErrorCount: 16,
|
|
},
|
|
},
|
|
},
|
|
Sensors: models.HostSensorSummary{
|
|
SMART: []models.HostDiskSMART{
|
|
{Device: "sdb [sat]", Serial: "PARITY-1"},
|
|
},
|
|
},
|
|
}
|
|
|
|
resource, _ := resourceFromHostSMARTDisk(host, host.Sensors.SMART[0])
|
|
if resource.PhysicalDisk == nil {
|
|
t.Fatal("expected physical disk metadata")
|
|
}
|
|
if resource.PhysicalDisk.StorageRole != "parity" {
|
|
t.Fatalf("storageRole = %q, want parity", resource.PhysicalDisk.StorageRole)
|
|
}
|
|
if resource.PhysicalDisk.StorageGroup != "unraid-array" {
|
|
t.Fatalf("storageGroup = %q, want unraid-array", resource.PhysicalDisk.StorageGroup)
|
|
}
|
|
if resource.PhysicalDisk.SizeBytes != 6_000_000_000_000 {
|
|
t.Fatalf("sizeBytes = %d, want unraid disk size", resource.PhysicalDisk.SizeBytes)
|
|
}
|
|
if resource.PhysicalDisk.Health != "PASSED" {
|
|
t.Fatalf("health = %q, want PASSED from Unraid online state", resource.PhysicalDisk.Health)
|
|
}
|
|
if !resource.PhysicalDisk.SpunDown || resource.PhysicalDisk.ReadCount != 11 || resource.PhysicalDisk.WriteCount != 12 || resource.PhysicalDisk.ErrorCount != 16 {
|
|
t.Fatalf("expected native Unraid disk counters, got %+v", resource.PhysicalDisk)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromHostSMARTDiskInfersUnraidArrayRole(t *testing.T) {
|
|
host := models.Host{
|
|
ID: "tower-host",
|
|
Hostname: "tower",
|
|
LastSeen: time.Now().UTC(),
|
|
Unraid: &models.HostUnraidStorage{
|
|
ArrayStarted: true,
|
|
Disks: []models.HostUnraidDisk{
|
|
{
|
|
Name: "md1p1",
|
|
Device: "/dev/sde",
|
|
Status: "online",
|
|
SizeBytes: 6_000_000_000_000,
|
|
Slot: 1,
|
|
},
|
|
},
|
|
},
|
|
Sensors: models.HostSensorSummary{
|
|
SMART: []models.HostDiskSMART{
|
|
{Device: "sde [sat]", Health: "UNKNOWN"},
|
|
},
|
|
},
|
|
}
|
|
|
|
resource, _ := resourceFromHostSMARTDisk(host, host.Sensors.SMART[0])
|
|
if resource.PhysicalDisk == nil {
|
|
t.Fatal("expected physical disk metadata")
|
|
}
|
|
if resource.PhysicalDisk.StorageRole != "data" {
|
|
t.Fatalf("storageRole = %q, want data", resource.PhysicalDisk.StorageRole)
|
|
}
|
|
if resource.PhysicalDisk.StorageGroup != "unraid-array" {
|
|
t.Fatalf("storageGroup = %q, want unraid-array", resource.PhysicalDisk.StorageGroup)
|
|
}
|
|
if resource.PhysicalDisk.SizeBytes != 6_000_000_000_000 {
|
|
t.Fatalf("sizeBytes = %d, want unraid disk size", resource.PhysicalDisk.SizeBytes)
|
|
}
|
|
if resource.PhysicalDisk.Health != "PASSED" {
|
|
t.Fatalf("health = %q, want PASSED from Unraid online state", resource.PhysicalDisk.Health)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromHostSMARTDiskNormalizesLegacyUnraidKiBSize(t *testing.T) {
|
|
const legacyDiskKiB = int64(5_860_522_532)
|
|
const normalizedDiskBytes = legacyDiskKiB * 1024
|
|
host := models.Host{
|
|
ID: "tower-host",
|
|
Hostname: "tower",
|
|
LastSeen: time.Now().UTC(),
|
|
Disks: []models.Disk{
|
|
{
|
|
Mountpoint: "/mnt/user",
|
|
Total: normalizedDiskBytes * 4,
|
|
Used: normalizedDiskBytes * 3,
|
|
Free: normalizedDiskBytes,
|
|
Usage: 75,
|
|
},
|
|
},
|
|
Unraid: &models.HostUnraidStorage{
|
|
ArrayStarted: true,
|
|
Disks: []models.HostUnraidDisk{
|
|
{Name: "md1p1", Device: "/dev/sde", Status: "online", SizeBytes: legacyDiskKiB, Slot: 1},
|
|
{Name: "md2p1", Device: "/dev/sdd", Status: "online", SizeBytes: legacyDiskKiB, Slot: 2},
|
|
{Name: "md3p1", Device: "/dev/sdb", Status: "online", SizeBytes: legacyDiskKiB, Slot: 3},
|
|
{Name: "md4p1", Device: "/dev/sdc", Status: "online", SizeBytes: legacyDiskKiB, Slot: 4},
|
|
},
|
|
},
|
|
Sensors: models.HostSensorSummary{
|
|
SMART: []models.HostDiskSMART{
|
|
{Device: "sde [sat]", Health: "UNKNOWN"},
|
|
},
|
|
},
|
|
}
|
|
|
|
resource, _ := resourceFromHostSMARTDisk(host, host.Sensors.SMART[0])
|
|
if resource.PhysicalDisk == nil {
|
|
t.Fatal("expected physical disk metadata")
|
|
}
|
|
if resource.PhysicalDisk.SizeBytes != normalizedDiskBytes {
|
|
t.Fatalf("sizeBytes = %d, want normalized Unraid bytes %d", resource.PhysicalDisk.SizeBytes, normalizedDiskBytes)
|
|
}
|
|
|
|
agentResource, _ := resourceFromHost(host)
|
|
if agentResource.Agent == nil || agentResource.Agent.Unraid == nil || len(agentResource.Agent.Unraid.Disks) == 0 {
|
|
t.Fatalf("expected agent Unraid metadata, got %+v", agentResource.Agent)
|
|
}
|
|
if got := agentResource.Agent.Unraid.Disks[0].SizeBytes; got != normalizedDiskBytes {
|
|
t.Fatalf("agent Unraid disk sizeBytes = %d, want %d", got, normalizedDiskBytes)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromKubernetesDeployment_PopulatesMetricsUnderMockMode(t *testing.T) {
|
|
mockruntime.SetEnabled(true)
|
|
t.Cleanup(func() { mockruntime.SetEnabled(false) })
|
|
|
|
cluster := models.KubernetesCluster{
|
|
ID: "cluster-1",
|
|
Name: "production",
|
|
}
|
|
deployment := models.KubernetesDeployment{
|
|
UID: "dep-uid-1",
|
|
Name: "checkout-api",
|
|
Namespace: "services",
|
|
DesiredReplicas: 3,
|
|
UpdatedReplicas: 3,
|
|
ReadyReplicas: 2,
|
|
AvailableReplicas: 2,
|
|
}
|
|
|
|
resource, _ := resourceFromKubernetesDeployment(cluster, deployment, nil)
|
|
if resource.Metrics == nil {
|
|
t.Fatal("expected mock-mode deployment to carry synthetic Metrics, got nil")
|
|
}
|
|
if resource.Metrics.CPU == nil || resource.Metrics.CPU.Percent <= 0 {
|
|
t.Fatalf("expected non-zero CPU percent, got %+v", resource.Metrics.CPU)
|
|
}
|
|
if resource.Metrics.Memory == nil || resource.Metrics.Memory.Percent <= 0 {
|
|
t.Fatalf("expected non-zero Memory percent, got %+v", resource.Metrics.Memory)
|
|
}
|
|
if resource.Metrics.Disk == nil || resource.Metrics.Disk.Percent < 0 {
|
|
t.Fatalf("expected Disk metric, got %+v", resource.Metrics.Disk)
|
|
}
|
|
}
|
|
|
|
func TestResourceFromKubernetesDeployment_NilMetricsOutsideMockMode(t *testing.T) {
|
|
mockruntime.SetEnabled(false)
|
|
cluster := models.KubernetesCluster{ID: "cluster-2", Name: "live"}
|
|
deployment := models.KubernetesDeployment{
|
|
Name: "frontend", Namespace: "web", DesiredReplicas: 2, ReadyReplicas: 2, AvailableReplicas: 2,
|
|
}
|
|
resource, _ := resourceFromKubernetesDeployment(cluster, deployment, nil)
|
|
if resource.Metrics != nil {
|
|
t.Fatalf("expected nil Metrics outside mock mode (no canonical aggregation yet), got %+v", resource.Metrics)
|
|
}
|
|
}
|