mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-10 03:51:54 +00:00
386 lines
13 KiB
Go
386 lines
13 KiB
Go
package unifiedresources
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
)
|
|
|
|
func TestIngestSnapshotIncludesKubernetesHierarchy(t *testing.T) {
|
|
enableMockMode(t)
|
|
|
|
now := time.Now().UTC()
|
|
podStart := now.Add(-15 * time.Minute)
|
|
snapshot := models.StateSnapshot{
|
|
KubernetesClusters: []models.KubernetesCluster{
|
|
{
|
|
ID: "cluster-1",
|
|
AgentID: "k8s-agent-1",
|
|
Name: "prod-k8s",
|
|
Context: "prod",
|
|
Version: "1.31.2",
|
|
Status: "online",
|
|
LastSeen: now,
|
|
IntervalSeconds: 30,
|
|
Nodes: []models.KubernetesNode{
|
|
{
|
|
UID: "node-uid-1",
|
|
Name: "worker-1",
|
|
Ready: true,
|
|
Roles: []string{"worker"},
|
|
KubeletVersion: "v1.31.2",
|
|
},
|
|
},
|
|
Pods: []models.KubernetesPod{
|
|
{
|
|
UID: "pod-uid-1",
|
|
Name: "api-123",
|
|
Namespace: "default",
|
|
NodeName: "worker-1",
|
|
Phase: "Running",
|
|
CreatedAt: now.Add(-20 * time.Minute),
|
|
StartTime: &podStart,
|
|
Containers: []models.KubernetesPodContainer{
|
|
{Name: "api", Image: "ghcr.io/acme/api:1.2.3", Ready: true, State: "Running"},
|
|
},
|
|
},
|
|
},
|
|
Deployments: []models.KubernetesDeployment{
|
|
{
|
|
UID: "deployment-uid-1",
|
|
Name: "api",
|
|
Namespace: "default",
|
|
DesiredReplicas: 3,
|
|
AvailableReplicas: 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
registry := NewRegistry(NewMemoryStore())
|
|
registry.IngestSnapshot(snapshot)
|
|
|
|
resources := registry.List()
|
|
if len(resources) != 4 {
|
|
t.Fatalf("expected 4 kubernetes resources, got %d", len(resources))
|
|
}
|
|
|
|
var clusterResource *Resource
|
|
var nodeResource *Resource
|
|
var podResource *Resource
|
|
var deploymentResource *Resource
|
|
|
|
for i := range resources {
|
|
resource := resources[i]
|
|
switch resource.Type {
|
|
case ResourceTypeK8sCluster:
|
|
clusterResource = &resource
|
|
case ResourceTypeK8sNode:
|
|
nodeResource = &resource
|
|
case ResourceTypePod:
|
|
podResource = &resource
|
|
case ResourceTypeK8sDeployment:
|
|
deploymentResource = &resource
|
|
}
|
|
}
|
|
|
|
if clusterResource == nil {
|
|
t.Fatal("expected kubernetes cluster resource")
|
|
}
|
|
if nodeResource == nil {
|
|
t.Fatal("expected kubernetes node resource")
|
|
}
|
|
if podResource == nil {
|
|
t.Fatal("expected kubernetes pod resource")
|
|
}
|
|
if deploymentResource == nil {
|
|
t.Fatal("expected kubernetes deployment resource")
|
|
}
|
|
|
|
if !containsDataSource(clusterResource.Sources, SourceK8s) {
|
|
t.Fatalf("expected cluster source to include kubernetes, got %+v", clusterResource.Sources)
|
|
}
|
|
if nodeResource.ParentID == nil || *nodeResource.ParentID != clusterResource.ID {
|
|
t.Fatalf("expected node parent %q, got %+v", clusterResource.ID, nodeResource.ParentID)
|
|
}
|
|
if podResource.ParentID == nil || *podResource.ParentID != clusterResource.ID {
|
|
t.Fatalf("expected pod parent %q, got %+v", clusterResource.ID, podResource.ParentID)
|
|
}
|
|
if deploymentResource.ParentID == nil || *deploymentResource.ParentID != clusterResource.ID {
|
|
t.Fatalf("expected deployment parent %q, got %+v", clusterResource.ID, deploymentResource.ParentID)
|
|
}
|
|
|
|
if podResource.Kubernetes == nil || podResource.Kubernetes.Namespace != "default" {
|
|
t.Fatalf("expected pod namespace metadata, got %+v", podResource.Kubernetes)
|
|
}
|
|
if podResource.Kubernetes.MetricCapabilities == nil {
|
|
t.Fatalf("expected kubernetes metric capabilities on pod resource, got nil")
|
|
}
|
|
if podResource.Kubernetes.MetricCapabilities.NodeCPUMemory {
|
|
t.Fatalf("expected node CPU/memory capability to be false without linked hosts or node usage metrics, got %+v", podResource.Kubernetes.MetricCapabilities)
|
|
}
|
|
if podResource.Kubernetes.MetricCapabilities.NodeTelemetry {
|
|
t.Fatalf("expected node telemetry capability to be false without linked host agent, got %+v", podResource.Kubernetes.MetricCapabilities)
|
|
}
|
|
if podResource.Kubernetes.MetricCapabilities.PodDiskIO {
|
|
t.Fatalf("expected pod disk I/O capability to remain false, got %+v", podResource.Kubernetes.MetricCapabilities)
|
|
}
|
|
if podResource.Kubernetes.UptimeSeconds <= 0 {
|
|
t.Fatalf("expected pod uptimeSeconds to be populated, got %d", podResource.Kubernetes.UptimeSeconds)
|
|
}
|
|
if podResource.Metrics == nil || podResource.Metrics.CPU == nil || podResource.Metrics.CPU.Value <= 0 {
|
|
t.Fatalf("expected pod cpu metric in mock mode, got %+v", podResource.Metrics)
|
|
}
|
|
if nodeResource.Metrics == nil {
|
|
t.Fatalf("expected node metrics payload to exist, got nil")
|
|
}
|
|
if nodeResource.Metrics.CPU != nil || nodeResource.Metrics.Memory != nil || nodeResource.Metrics.Disk != nil ||
|
|
nodeResource.Metrics.NetIn != nil || nodeResource.Metrics.NetOut != nil ||
|
|
nodeResource.Metrics.DiskRead != nil || nodeResource.Metrics.DiskWrite != nil {
|
|
t.Fatalf("expected node usage metrics to be absent, got %+v", nodeResource.Metrics)
|
|
}
|
|
if nodeResource.Kubernetes == nil {
|
|
t.Fatalf("expected node kubernetes metadata, got nil")
|
|
}
|
|
if nodeResource.Kubernetes.UptimeSeconds != 0 {
|
|
t.Fatalf("expected node uptimeSeconds to be unset, got %d", nodeResource.Kubernetes.UptimeSeconds)
|
|
}
|
|
if nodeResource.Kubernetes.Temperature != nil {
|
|
t.Fatalf("expected node temperature to be unset, got %+v", nodeResource.Kubernetes.Temperature)
|
|
}
|
|
if clusterResource.Metrics == nil {
|
|
t.Fatalf("expected cluster metrics payload to exist, got nil")
|
|
}
|
|
if clusterResource.Metrics.CPU != nil || clusterResource.Metrics.Memory != nil || clusterResource.Metrics.Disk != nil ||
|
|
clusterResource.Metrics.NetIn != nil || clusterResource.Metrics.NetOut != nil ||
|
|
clusterResource.Metrics.DiskRead != nil || clusterResource.Metrics.DiskWrite != nil {
|
|
t.Fatalf("expected cluster usage metrics to be absent, got %+v", clusterResource.Metrics)
|
|
}
|
|
if clusterResource.Kubernetes == nil {
|
|
t.Fatalf("expected cluster kubernetes metadata, got nil")
|
|
}
|
|
if clusterResource.Kubernetes.MetricCapabilities == nil {
|
|
t.Fatalf("expected cluster kubernetes metric capabilities, got nil")
|
|
}
|
|
if clusterResource.Kubernetes.UptimeSeconds != 0 {
|
|
t.Fatalf("expected cluster uptimeSeconds to be unset, got %d", clusterResource.Kubernetes.UptimeSeconds)
|
|
}
|
|
if clusterResource.Kubernetes.Temperature != nil {
|
|
t.Fatalf("expected cluster temperature to be unset, got %+v", clusterResource.Kubernetes.Temperature)
|
|
}
|
|
}
|
|
|
|
func TestIngestSnapshotLinksKubernetesNodesToHostAgentMetrics(t *testing.T) {
|
|
now := time.Now().UTC()
|
|
snapshot := models.StateSnapshot{
|
|
Hosts: []models.Host{
|
|
{
|
|
ID: "host-worker-1",
|
|
Hostname: "worker-1.example.local",
|
|
Status: "online",
|
|
LastSeen: now,
|
|
UptimeSeconds: 7200,
|
|
CPUUsage: 42,
|
|
Memory: models.Memory{
|
|
Total: 16 * 1024 * 1024 * 1024,
|
|
Used: 8 * 1024 * 1024 * 1024,
|
|
Usage: 50,
|
|
},
|
|
Disks: []models.Disk{
|
|
{Total: 500 * 1024 * 1024 * 1024, Used: 200 * 1024 * 1024 * 1024, Usage: 40},
|
|
},
|
|
NetInRate: 1200,
|
|
NetOutRate: 2400,
|
|
DiskReadRate: 3600,
|
|
DiskWriteRate: 4800,
|
|
Sensors: models.HostSensorSummary{
|
|
TemperatureCelsius: map[string]float64{"cpu.package": 58.5},
|
|
},
|
|
},
|
|
},
|
|
KubernetesClusters: []models.KubernetesCluster{
|
|
{
|
|
ID: "cluster-1",
|
|
AgentID: "k8s-agent-1",
|
|
Name: "prod-k8s",
|
|
Status: "online",
|
|
LastSeen: now,
|
|
Nodes: []models.KubernetesNode{
|
|
{
|
|
UID: "node-uid-1",
|
|
Name: "worker-1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
registry := NewRegistry(NewMemoryStore())
|
|
registry.IngestSnapshot(snapshot)
|
|
|
|
resources := registry.List()
|
|
if len(resources) != 2 {
|
|
t.Fatalf("expected 2 resources (cluster + merged agent), got %d", len(resources))
|
|
}
|
|
|
|
var clusterResource *Resource
|
|
var agentResource *Resource
|
|
for i := range resources {
|
|
resource := resources[i]
|
|
switch resource.Type {
|
|
case ResourceTypeK8sCluster:
|
|
clusterResource = &resource
|
|
case ResourceTypeAgent:
|
|
agentResource = &resource
|
|
}
|
|
}
|
|
|
|
if agentResource == nil {
|
|
t.Fatal("expected linked kubernetes node to merge into agent resource")
|
|
}
|
|
if agentResource.Metrics == nil || agentResource.Metrics.CPU == nil || agentResource.Metrics.CPU.Value <= 0 {
|
|
t.Fatalf("expected merged agent cpu metric from linked host, got %+v", agentResource.Metrics)
|
|
}
|
|
if agentResource.Metrics.NetIn == nil || agentResource.Metrics.NetIn.Value != 1200 {
|
|
t.Fatalf("expected merged agent netIn metric from linked host, got %+v", agentResource.Metrics)
|
|
}
|
|
if agentResource.Kubernetes == nil || agentResource.Kubernetes.UptimeSeconds != 7200 {
|
|
t.Fatalf("expected kubernetes node payload to retain linked host uptime, got %+v", agentResource.Kubernetes)
|
|
}
|
|
if agentResource.Kubernetes.Temperature == nil || *agentResource.Kubernetes.Temperature <= 0 {
|
|
t.Fatalf("expected kubernetes node payload to retain linked host temperature, got %+v", agentResource.Kubernetes)
|
|
}
|
|
if agentResource.Kubernetes.MetricCapabilities == nil {
|
|
t.Fatalf("expected kubernetes metric capabilities on node resource, got nil")
|
|
}
|
|
if !agentResource.Kubernetes.MetricCapabilities.NodeCPUMemory {
|
|
t.Fatalf("expected node CPU/memory capability from linked host, got %+v", agentResource.Kubernetes.MetricCapabilities)
|
|
}
|
|
if !agentResource.Kubernetes.MetricCapabilities.NodeTelemetry {
|
|
t.Fatalf("expected node telemetry capability from linked host, got %+v", agentResource.Kubernetes.MetricCapabilities)
|
|
}
|
|
if !hasDataSource(agentResource.Sources, SourceAgent) || !hasDataSource(agentResource.Sources, SourceK8s) {
|
|
t.Fatalf("expected merged agent sources to include agent+kubernetes, got %+v", agentResource.Sources)
|
|
}
|
|
|
|
if clusterResource == nil {
|
|
t.Fatal("expected kubernetes cluster resource")
|
|
}
|
|
if clusterResource.Metrics == nil || clusterResource.Metrics.CPU == nil || clusterResource.Metrics.CPU.Value <= 0 {
|
|
t.Fatalf("expected kubernetes cluster metrics aggregated from linked hosts, got %+v", clusterResource.Metrics)
|
|
}
|
|
}
|
|
|
|
func TestIngestSnapshotKubernetesCapabilitiesFromK8sMetrics(t *testing.T) {
|
|
now := time.Now().UTC()
|
|
snapshot := models.StateSnapshot{
|
|
KubernetesClusters: []models.KubernetesCluster{
|
|
{
|
|
ID: "cluster-k8s-only",
|
|
AgentID: "k8s-agent-only",
|
|
Name: "k8s-only",
|
|
Status: "online",
|
|
LastSeen: now,
|
|
Nodes: []models.KubernetesNode{
|
|
{
|
|
UID: "node-uid-1",
|
|
Name: "worker-1",
|
|
UsageCPUMilliCores: 720,
|
|
UsageCPUPercent: 36,
|
|
UsageMemoryBytes: 6 * 1024 * 1024 * 1024,
|
|
},
|
|
},
|
|
Pods: []models.KubernetesPod{
|
|
{
|
|
UID: "pod-uid-1",
|
|
Name: "api-1",
|
|
Namespace: "default",
|
|
NodeName: "worker-1",
|
|
Phase: "Running",
|
|
UsageCPUMilliCores: 450,
|
|
UsageCPUPercent: 22,
|
|
UsageMemoryBytes: 850 * 1024 * 1024,
|
|
UsageMemoryPercent: 17,
|
|
NetworkRxBytes: 250000,
|
|
NetworkTxBytes: 190000,
|
|
NetInRate: 1300,
|
|
NetOutRate: 900,
|
|
EphemeralStorageUsedBytes: 3 * 1024 * 1024 * 1024,
|
|
EphemeralStorageCapacityBytes: 12 * 1024 * 1024 * 1024,
|
|
DiskUsagePercent: 25,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
registry := NewRegistry(NewMemoryStore())
|
|
registry.IngestSnapshot(snapshot)
|
|
|
|
resources := registry.List()
|
|
var clusterResource *Resource
|
|
for i := range resources {
|
|
resource := resources[i]
|
|
if resource.Type == ResourceTypeK8sCluster {
|
|
clusterResource = &resource
|
|
break
|
|
}
|
|
}
|
|
if clusterResource == nil {
|
|
t.Fatal("expected kubernetes cluster resource")
|
|
}
|
|
if clusterResource.Kubernetes == nil || clusterResource.Kubernetes.MetricCapabilities == nil {
|
|
t.Fatalf("expected kubernetes metric capabilities on cluster resource, got %+v", clusterResource.Kubernetes)
|
|
}
|
|
|
|
cap := clusterResource.Kubernetes.MetricCapabilities
|
|
if !cap.NodeCPUMemory {
|
|
t.Fatalf("expected node CPU/memory capability from k8s usage metrics, got %+v", cap)
|
|
}
|
|
if cap.NodeTelemetry {
|
|
t.Fatalf("expected node telemetry capability to be false without linked host agent, got %+v", cap)
|
|
}
|
|
if !cap.PodCPUMemory {
|
|
t.Fatalf("expected pod CPU/memory capability from k8s usage metrics, got %+v", cap)
|
|
}
|
|
if !cap.PodNetwork {
|
|
t.Fatalf("expected pod network capability from pod rates/bytes, got %+v", cap)
|
|
}
|
|
if !cap.PodEphemeralDisk {
|
|
t.Fatalf("expected pod ephemeral disk capability from summary metrics, got %+v", cap)
|
|
}
|
|
if cap.PodDiskIO {
|
|
t.Fatalf("expected pod disk I/O capability to remain false, got %+v", cap)
|
|
}
|
|
}
|
|
|
|
func TestIngestSnapshotSkipsHiddenKubernetesClusters(t *testing.T) {
|
|
snapshot := models.StateSnapshot{
|
|
KubernetesClusters: []models.KubernetesCluster{
|
|
{
|
|
ID: "cluster-hidden",
|
|
AgentID: "k8s-agent-hidden",
|
|
Name: "hidden",
|
|
Hidden: true,
|
|
Status: "online",
|
|
LastSeen: time.Now().UTC(),
|
|
Pods: []models.KubernetesPod{
|
|
{
|
|
UID: "pod-hidden",
|
|
Name: "pod-hidden",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
registry := NewRegistry(NewMemoryStore())
|
|
registry.IngestSnapshot(snapshot)
|
|
|
|
if got := len(registry.List()); got != 0 {
|
|
t.Fatalf("expected hidden kubernetes cluster to be skipped, got %d resources", got)
|
|
}
|
|
}
|