Pulse/internal/monitoring/kubernetes_agents_test.go

471 lines
22 KiB
Go

package monitoring
import (
"crypto/sha256"
"encoding/hex"
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
agentsk8s "github.com/rcourtman/pulse-go-rewrite/pkg/agents/kubernetes"
)
func newKubernetesTestMonitor() *Monitor {
return &Monitor{
state: models.NewState(),
config: &config.Config{},
removedKubernetesClusters: make(map[string]time.Time),
kubernetesTokenBindings: make(map[string]string),
}
}
func TestNormalizeKubernetesClusterIdentifier(t *testing.T) {
report := agentsk8s.Report{
Cluster: agentsk8s.ClusterInfo{ID: "cluster-1"},
Agent: agentsk8s.AgentInfo{ID: "agent-1"},
}
if got := normalizeKubernetesClusterIdentifier(report); got != "cluster-1" {
t.Fatalf("unexpected identifier: %s", got)
}
report.Cluster.ID = ""
if got := normalizeKubernetesClusterIdentifier(report); got != "agent-1" {
t.Fatalf("unexpected identifier: %s", got)
}
report.Agent.ID = ""
report.Cluster.Server = "https://server"
report.Cluster.Context = "ctx"
report.Cluster.Name = "name"
stableKey := "https://server|ctx|name"
sum := sha256.Sum256([]byte(stableKey))
expected := hex.EncodeToString(sum[:])
if got := normalizeKubernetesClusterIdentifier(report); got != expected {
t.Fatalf("unexpected hashed identifier: %s", got)
}
report.Cluster.Server = ""
report.Cluster.Context = ""
report.Cluster.Name = ""
if got := normalizeKubernetesClusterIdentifier(report); got != "" {
t.Fatalf("expected empty identifier, got %s", got)
}
}
func TestApplyKubernetesReport(t *testing.T) {
monitor := newKubernetesTestMonitor()
report := agentsk8s.Report{
Agent: agentsk8s.AgentInfo{ID: "agent-1", IntervalSeconds: 10},
Cluster: agentsk8s.ClusterInfo{ID: "cluster-1", Name: "cluster"},
}
cluster, err := monitor.ApplyKubernetesReport(report, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if cluster.ID != "cluster-1" || cluster.DisplayName != "cluster" {
t.Fatalf("unexpected cluster: %+v", cluster)
}
if !monitor.state.ConnectionHealth[kubernetesConnectionPrefix+"cluster-1"] {
t.Fatal("expected connection health to be true")
}
monitor.removedKubernetesClusters["cluster-2"] = time.Now()
report.Cluster.ID = "cluster-2"
if _, err := monitor.ApplyKubernetesReport(report, nil); err == nil {
t.Fatal("expected error for removed cluster")
}
token := &config.APITokenRecord{ID: "token-1", Name: "Token"}
monitor.kubernetesTokenBindings["token-1"] = "other-agent"
report.Cluster.ID = "cluster-3"
report.Agent.ID = "agent-1"
if _, err := monitor.ApplyKubernetesReport(report, token); err == nil {
t.Fatal("expected error for token bound to different agent")
}
}
func TestApplyKubernetesReportRefreshesUnifiedReadStateWithoutBroadcast(t *testing.T) {
monitor := newKubernetesTestMonitor()
adapter := unifiedresources.NewMonitorAdapter(unifiedresources.NewRegistry(nil))
monitor.SetResourceStore(adapter)
_, err := monitor.ApplyKubernetesReport(agentsk8s.Report{
Agent: agentsk8s.AgentInfo{ID: "headless-kubernetes-agent", IntervalSeconds: 30},
Cluster: agentsk8s.ClusterInfo{ID: "headless-kubernetes-cluster", Name: "headless-canary-cluster"},
}, nil)
if err != nil {
t.Fatalf("ApplyKubernetesReport: %v", err)
}
for _, cluster := range adapter.K8sClusters() {
if cluster != nil && cluster.ClusterID() == "headless-kubernetes-cluster" {
return
}
}
t.Fatal("accepted Kubernetes report did not refresh the canonical headless read state")
}
func TestApplyKubernetesReportPreservesNativeAPIInventory(t *testing.T) {
monitor := newKubernetesTestMonitor()
now := time.Date(2026, 5, 24, 8, 0, 0, 0, time.UTC)
allowExpansion := true
automountToken := true
report := agentsk8s.Report{
Agent: agentsk8s.AgentInfo{ID: "agent-1", IntervalSeconds: 10},
Cluster: agentsk8s.ClusterInfo{ID: "cluster-1", Name: "cluster"},
Namespaces: []agentsk8s.Namespace{{
UID: "ns-1",
Name: " services ",
Phase: " Active ",
CreatedAt: now,
Labels: map[string]string{"team": "checkout"},
}},
ReplicaSets: []agentsk8s.ReplicaSet{{UID: "rs-1", Name: "checkout-6d4", Namespace: "services", DesiredReplicas: 3, ReadyReplicas: 2, FullyLabeledReplicas: 3, ObservedGeneration: 7, OwnerKind: "Deployment", OwnerName: "checkout"}},
Services: []agentsk8s.Service{{
UID: "svc-1",
Name: " checkout ",
Namespace: " services ",
Type: " ClusterIP ",
ClusterIP: "10.96.0.10",
ExternalIPs: []string{"192.0.2.10"},
Ports: []agentsk8s.ServicePort{{Name: " http ", Protocol: " TCP ", Port: 80, TargetPort: "8080"}},
Selector: map[string]string{"app": "checkout"},
CreatedAt: now,
}},
Deployments: []agentsk8s.Deployment{{
UID: "deploy-1",
Name: "checkout-api",
Namespace: "services",
CreatedAt: now,
DesiredReplicas: 4,
UpdatedReplicas: 3,
ReadyReplicas: 2,
AvailableReplicas: 2,
ObservedGeneration: 12,
Labels: map[string]string{"app": "checkout"},
}},
StatefulSets: []agentsk8s.StatefulSet{{UID: "sts-1", Name: "db", Namespace: "services", DesiredReplicas: 3, ReadyReplicas: 2, ServiceName: "db-headless"}},
DaemonSets: []agentsk8s.DaemonSet{{UID: "ds-1", Name: "node-agent", Namespace: "kube-system", DesiredNumberScheduled: 3, NumberReady: 3}},
Jobs: []agentsk8s.Job{{UID: "job-1", Name: "backup", Namespace: "services", DesiredCompletions: 1, Succeeded: 1}},
CronJobs: []agentsk8s.CronJob{{UID: "cron-1", Name: "nightly", Namespace: "services", Schedule: "0 1 * * *"}},
Ingresses: []agentsk8s.Ingress{{UID: "ing-1", Name: "checkout", Namespace: "services", ClassName: "nginx", Hosts: []string{"checkout.example.test"}, Addresses: []string{"192.0.2.20"}, CreatedAt: now}},
EndpointSlices: []agentsk8s.EndpointSlice{{
UID: "eps-1", Name: "checkout-abc", Namespace: "services", AddressType: "IPv4", ServiceName: "checkout", EndpointCount: 3, ReadyEndpointCount: 2, Ports: []agentsk8s.EndpointPort{{Name: "http", Protocol: "TCP", Port: 80, AppProtocol: "kubernetes.io/http"}},
}},
NetworkPolicies: []agentsk8s.NetworkPolicy{{UID: "netpol-1", Name: "checkout-deny", Namespace: "services", PolicyTypes: []string{"Ingress", "Egress"}, IngressRuleCount: 1, EgressRuleCount: 2}},
PersistentVolumes: []agentsk8s.PersistentVolume{{
UID: "pv-1", Name: "pv-checkout", Phase: " Bound ", StorageClass: "fast", CapacityBytes: 10_000, AccessModes: []string{"ReadWriteOnce"}, ReclaimPolicy: "Retain", ClaimNamespace: "services", ClaimName: "checkout-data", CreatedAt: now,
}},
PersistentVolumeClaims: []agentsk8s.PersistentVolumeClaim{{
UID: "pvc-1", Name: "checkout-data", Namespace: "services", Phase: " Bound ", StorageClass: "fast", RequestedBytes: 10_000, CapacityBytes: 10_000, AccessModes: []string{"ReadWriteOnce"}, VolumeName: "pv-checkout", CreatedAt: now,
}},
StorageClasses: []agentsk8s.StorageClass{{UID: "sc-1", Name: "fast", Provisioner: "csi.example.test", ReclaimPolicy: "Delete", VolumeBindingMode: "WaitForFirstConsumer", AllowVolumeExpansion: &allowExpansion, ParameterKeys: []string{"type"}}},
ConfigMaps: []agentsk8s.ConfigMap{{UID: "cm-1", Name: "checkout-config", Namespace: "services", DataKeys: []string{"app.yaml"}, BinaryDataKeys: []string{"logo.png"}, Immutable: true, MetadataOnly: true}},
Secrets: []agentsk8s.Secret{{UID: "sec-1", Name: "checkout-secret", Namespace: "services", Type: "Opaque", DataKeys: []string{"token"}, Immutable: true, MetadataOnly: true}},
ServiceAccounts: []agentsk8s.ServiceAccount{{UID: "sa-1", Name: "checkout", Namespace: "services", AutomountServiceAccountToken: &automountToken, SecretCount: 1, ImagePullSecrets: []string{"pull-secret"}}},
Roles: []agentsk8s.Role{{UID: "role-1", Name: "checkout-runtime", Namespace: "services", RuleCount: 4}},
ClusterRoles: []agentsk8s.ClusterRole{{UID: "crole-1", Name: "platform-monitoring", RuleCount: 12, AggregationLabels: map[string]string{"rbac.authorization.k8s.io/aggregate-to-admin": "true"}}},
RoleBindings: []agentsk8s.RoleBinding{{UID: "rb-1", Name: "checkout-runtime", Namespace: "services", RoleKind: "Role", RoleName: "checkout-runtime", SubjectCount: 2, SubjectKinds: []string{"Group", "ServiceAccount"}}},
ClusterRoleBindings: []agentsk8s.ClusterRoleBinding{{UID: "crb-1", Name: "platform-monitoring", RoleKind: "ClusterRole", RoleName: "platform-monitoring", SubjectCount: 3, SubjectKinds: []string{"Group", "ServiceAccount", "User"}}},
ResourceQuotas: []agentsk8s.ResourceQuota{{UID: "rq-1", Name: "services-quota", Namespace: "services", Hard: map[string]string{"pods": "10"}, Used: map[string]string{"pods": "3"}}},
LimitRanges: []agentsk8s.LimitRange{{UID: "lr-1", Name: "services-limits", Namespace: "services", LimitTypes: []string{"Container"}}},
PodDisruptionBudgets: []agentsk8s.PodDisruptionBudget{{
UID: "pdb-1", Name: "checkout-pdb", Namespace: "services", MinAvailable: "1", DesiredHealthy: 1, CurrentHealthy: 1, DisruptionsAllowed: 1, ExpectedPods: 2,
}},
HorizontalPodAutoscalers: []agentsk8s.HorizontalPodAutoscaler{{
UID: "hpa-1", Name: "checkout-hpa", Namespace: "services", TargetKind: "Deployment", TargetName: "checkout", MinReplicas: 2, MaxReplicas: 10, CurrentReplicas: 2, DesiredReplicas: 3, MetricTypes: []string{"Resource:cpu"},
}},
Events: []agentsk8s.Event{{UID: "evt-1", Name: "checkout.1", Namespace: "services", Type: "Warning", Reason: "BackOff", Message: "retrying", InvolvedKind: "Pod", InvolvedName: "checkout-0", Count: 2}},
Timestamp: now,
}
cluster, err := monitor.ApplyKubernetesReport(report, nil)
if err != nil {
t.Fatalf("ApplyKubernetesReport: %v", err)
}
if len(cluster.Namespaces) != 1 || cluster.Namespaces[0].Name != "services" {
t.Fatalf("expected namespace inventory, got %+v", cluster.Namespaces)
}
if len(cluster.Services) != 1 || cluster.Services[0].ServiceType != "ClusterIP" || cluster.Services[0].Ports[0].TargetPort != "8080" {
t.Fatalf("expected service inventory, got %+v", cluster.Services)
}
if len(cluster.ReplicaSets) != 1 || cluster.ReplicaSets[0].OwnerName != "checkout" || cluster.ReplicaSets[0].FullyLabeledReplicas != 3 {
t.Fatalf("expected replicaset inventory, got %+v", cluster.ReplicaSets)
}
if len(cluster.Deployments) != 1 || cluster.Deployments[0].CreatedAt.IsZero() || cluster.Deployments[0].ObservedGeneration != 12 {
t.Fatalf("expected deployment API metadata inventory, got %+v", cluster.Deployments)
}
if len(cluster.StatefulSets) != 1 || cluster.StatefulSets[0].ReadyReplicas != 2 {
t.Fatalf("expected statefulset inventory, got %+v", cluster.StatefulSets)
}
if len(cluster.DaemonSets) != 1 || cluster.DaemonSets[0].NumberReady != 3 {
t.Fatalf("expected daemonset inventory, got %+v", cluster.DaemonSets)
}
if len(cluster.Jobs) != 1 || cluster.Jobs[0].Succeeded != 1 {
t.Fatalf("expected job inventory, got %+v", cluster.Jobs)
}
if len(cluster.CronJobs) != 1 || cluster.CronJobs[0].Schedule != "0 1 * * *" {
t.Fatalf("expected cronjob inventory, got %+v", cluster.CronJobs)
}
if len(cluster.Ingresses) != 1 || cluster.Ingresses[0].Hosts[0] != "checkout.example.test" {
t.Fatalf("expected ingress inventory, got %+v", cluster.Ingresses)
}
if len(cluster.EndpointSlices) != 1 || cluster.EndpointSlices[0].ServiceName != "checkout" || cluster.EndpointSlices[0].ReadyEndpointCount != 2 {
t.Fatalf("expected endpoint slice inventory, got %+v", cluster.EndpointSlices)
}
if len(cluster.NetworkPolicies) != 1 || cluster.NetworkPolicies[0].EgressRuleCount != 2 {
t.Fatalf("expected network policy inventory, got %+v", cluster.NetworkPolicies)
}
if len(cluster.PersistentVolumes) != 1 || cluster.PersistentVolumes[0].ClaimName != "checkout-data" {
t.Fatalf("expected PV inventory, got %+v", cluster.PersistentVolumes)
}
if len(cluster.PersistentVolumeClaims) != 1 || cluster.PersistentVolumeClaims[0].VolumeName != "pv-checkout" {
t.Fatalf("expected PVC inventory, got %+v", cluster.PersistentVolumeClaims)
}
if len(cluster.StorageClasses) != 1 || cluster.StorageClasses[0].Provisioner != "csi.example.test" || cluster.StorageClasses[0].AllowVolumeExpansion == nil || !*cluster.StorageClasses[0].AllowVolumeExpansion {
t.Fatalf("expected storage class inventory, got %+v", cluster.StorageClasses)
}
if len(cluster.ConfigMaps) != 1 || cluster.ConfigMaps[0].DataKeys[0] != "app.yaml" || !cluster.ConfigMaps[0].Immutable || !cluster.ConfigMaps[0].MetadataOnly {
t.Fatalf("expected configmap inventory, got %+v", cluster.ConfigMaps)
}
if len(cluster.Secrets) != 1 || cluster.Secrets[0].Type != "Opaque" || cluster.Secrets[0].DataKeys[0] != "token" || !cluster.Secrets[0].Immutable || !cluster.Secrets[0].MetadataOnly {
t.Fatalf("expected secret metadata inventory, got %+v", cluster.Secrets)
}
if len(cluster.ServiceAccounts) != 1 || cluster.ServiceAccounts[0].SecretCount != 1 || cluster.ServiceAccounts[0].AutomountServiceAccountToken == nil || !*cluster.ServiceAccounts[0].AutomountServiceAccountToken {
t.Fatalf("expected serviceaccount inventory, got %+v", cluster.ServiceAccounts)
}
if len(cluster.Roles) != 1 || cluster.Roles[0].Name != "checkout-runtime" || cluster.Roles[0].RuleCount != 4 {
t.Fatalf("expected role inventory, got %+v", cluster.Roles)
}
if len(cluster.ClusterRoles) != 1 || cluster.ClusterRoles[0].RuleCount != 12 || cluster.ClusterRoles[0].AggregationLabels["rbac.authorization.k8s.io/aggregate-to-admin"] != "true" {
t.Fatalf("expected clusterrole inventory, got %+v", cluster.ClusterRoles)
}
if len(cluster.RoleBindings) != 1 || cluster.RoleBindings[0].RoleKind != "Role" || cluster.RoleBindings[0].SubjectCount != 2 || len(cluster.RoleBindings[0].SubjectKinds) != 2 {
t.Fatalf("expected rolebinding inventory, got %+v", cluster.RoleBindings)
}
if len(cluster.ClusterRoleBindings) != 1 || cluster.ClusterRoleBindings[0].RoleKind != "ClusterRole" || cluster.ClusterRoleBindings[0].SubjectCount != 3 {
t.Fatalf("expected clusterrolebinding inventory, got %+v", cluster.ClusterRoleBindings)
}
if len(cluster.ResourceQuotas) != 1 || cluster.ResourceQuotas[0].Hard["pods"] != "10" || cluster.ResourceQuotas[0].Used["pods"] != "3" {
t.Fatalf("expected resourcequota inventory, got %+v", cluster.ResourceQuotas)
}
if len(cluster.LimitRanges) != 1 || cluster.LimitRanges[0].LimitTypes[0] != "Container" {
t.Fatalf("expected limitrange inventory, got %+v", cluster.LimitRanges)
}
if len(cluster.PodDisruptionBudgets) != 1 || cluster.PodDisruptionBudgets[0].ExpectedPods != 2 || cluster.PodDisruptionBudgets[0].MinAvailable != "1" {
t.Fatalf("expected poddisruptionbudget inventory, got %+v", cluster.PodDisruptionBudgets)
}
if len(cluster.HorizontalPodAutoscalers) != 1 || cluster.HorizontalPodAutoscalers[0].TargetName != "checkout" || cluster.HorizontalPodAutoscalers[0].MetricTypes[0] != "Resource:cpu" {
t.Fatalf("expected horizontalpodautoscaler inventory, got %+v", cluster.HorizontalPodAutoscalers)
}
if len(cluster.Events) != 1 || cluster.Events[0].Reason != "BackOff" || cluster.Events[0].Count != 2 {
t.Fatalf("expected event inventory, got %+v", cluster.Events)
}
}
func TestApplyKubernetesReport_PodNetworkAndEphemeralMetrics(t *testing.T) {
monitor := newKubernetesTestMonitor()
monitor.rateTracker = NewRateTracker()
monitor.metricsHistory = NewMetricsHistory(10, time.Hour)
baseTime := time.Now().Add(-20 * time.Second).UTC()
report := agentsk8s.Report{
Agent: agentsk8s.AgentInfo{ID: "agent-1", IntervalSeconds: 10},
Cluster: agentsk8s.ClusterInfo{ID: "cluster-1", Name: "cluster"},
Nodes: []agentsk8s.Node{
{
Name: "node-a",
Capacity: agentsk8s.NodeResources{
CPUCores: 4,
MemoryBytes: 16 * 1024 * 1024 * 1024,
},
Allocatable: agentsk8s.NodeResources{
CPUCores: 4,
MemoryBytes: 16 * 1024 * 1024 * 1024,
},
},
},
Pods: []agentsk8s.Pod{
{
UID: "pod-1",
Name: "api-0",
Namespace: "default",
NodeName: "node-a",
Phase: "Running",
Usage: &agentsk8s.PodUsage{
CPUMilliCores: 400,
MemoryBytes: 1024 * 1024 * 1024,
NetworkRxBytes: 1000,
NetworkTxBytes: 2000,
EphemeralStorageUsedBytes: 500 * 1024 * 1024,
EphemeralStorageCapacityBytes: 2 * 1024 * 1024 * 1024,
},
},
},
Timestamp: baseTime,
}
firstCluster, err := monitor.ApplyKubernetesReport(report, nil)
if err != nil {
t.Fatalf("ApplyKubernetesReport(first): %v", err)
}
if len(firstCluster.Pods) != 1 {
t.Fatalf("expected one pod, got %d", len(firstCluster.Pods))
}
firstPod := firstCluster.Pods[0]
if firstPod.DiskUsagePercent <= 0 {
t.Fatalf("expected non-zero disk usage percent from ephemeral storage, got %f", firstPod.DiskUsagePercent)
}
if firstPod.NetInRate != 0 || firstPod.NetOutRate != 0 {
t.Fatalf("expected first sample network rates to be zero, got in=%f out=%f", firstPod.NetInRate, firstPod.NetOutRate)
}
report.Timestamp = baseTime.Add(10 * time.Second)
report.Pods[0].Usage.NetworkRxBytes = 61000
report.Pods[0].Usage.NetworkTxBytes = 42000
secondCluster, err := monitor.ApplyKubernetesReport(report, nil)
if err != nil {
t.Fatalf("ApplyKubernetesReport(second): %v", err)
}
secondPod := secondCluster.Pods[0]
if secondPod.NetInRate <= 0 || secondPod.NetOutRate <= 0 {
t.Fatalf("expected positive network rates, got in=%f out=%f", secondPod.NetInRate, secondPod.NetOutRate)
}
metricID := kubernetesPodMetricID(secondCluster, secondPod)
if metricID == "" {
t.Fatal("expected kubernetes pod metric id")
}
netInPoints := monitor.metricsHistory.GetGuestMetrics(metricID, "netin", time.Hour)
if len(netInPoints) == 0 {
t.Fatal("expected netin points to be recorded")
}
diskPoints := monitor.metricsHistory.GetGuestMetrics(metricID, "disk", time.Hour)
if len(diskPoints) == 0 {
t.Fatal("expected disk points to be recorded")
}
}
func TestRemoveAndReenrollKubernetesCluster(t *testing.T) {
monitor := newKubernetesTestMonitor()
monitor.kubernetesTokenBindings["token-1"] = "agent-1"
monitor.config.APITokens = []config.APITokenRecord{{ID: "token-1"}}
monitor.state.UpsertKubernetesCluster(models.KubernetesCluster{
ID: "cluster-1",
Name: "cluster",
DisplayName: "cluster",
TokenID: "token-1",
TokenName: "Token",
})
monitor.state.SetConnectionHealth(kubernetesConnectionPrefix+"cluster-1", true)
_, err := monitor.RemoveKubernetesCluster("cluster-1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(monitor.state.GetKubernetesClusters()) != 0 {
t.Fatal("expected cluster removed")
}
if _, exists := monitor.kubernetesTokenBindings["token-1"]; exists {
t.Fatal("expected token binding removed")
}
if _, exists := monitor.state.ConnectionHealth[kubernetesConnectionPrefix+"cluster-1"]; exists {
t.Fatal("expected connection health removed")
}
if len(monitor.state.GetRemovedKubernetesClusters()) != 1 {
t.Fatal("expected removed cluster entry")
}
if err := monitor.AllowKubernetesClusterReenroll("cluster-1"); err != nil {
t.Fatalf("unexpected reenroll error: %v", err)
}
if len(monitor.state.GetRemovedKubernetesClusters()) != 0 {
t.Fatal("expected removed entry cleared")
}
}
func TestKubernetesClusterUpdates(t *testing.T) {
monitor := newKubernetesTestMonitor()
monitor.state.UpsertKubernetesCluster(models.KubernetesCluster{
ID: "cluster-1",
Name: "cluster",
LastSeen: time.Now().Add(-10 * time.Second),
Status: "online",
IntervalSeconds: 5,
})
monitor.state.UpsertKubernetesCluster(models.KubernetesCluster{
ID: "cluster-2",
Name: "cluster2",
LastSeen: time.Now().Add(-10 * time.Hour),
Status: "online",
})
now := time.Now()
monitor.evaluateKubernetesAgents(now)
if monitor.state.ConnectionHealth[kubernetesConnectionPrefix+"cluster-1"] != true {
t.Fatal("expected cluster-1 healthy")
}
if monitor.state.ConnectionHealth[kubernetesConnectionPrefix+"cluster-2"] != false {
t.Fatal("expected cluster-2 unhealthy")
}
_, err := monitor.UnhideKubernetesCluster("cluster-1")
if err != nil {
t.Fatalf("unexpected unhide error: %v", err)
}
if _, err := monitor.MarkKubernetesClusterPendingUninstall("cluster-1"); err != nil {
t.Fatalf("unexpected pending uninstall error: %v", err)
}
if _, err := monitor.SetKubernetesClusterCustomDisplayName("cluster-1", "custom"); err != nil {
t.Fatalf("unexpected set display name error: %v", err)
}
}
func TestCleanupRemovedKubernetesClusters(t *testing.T) {
monitor := newKubernetesTestMonitor()
monitor.removedKubernetesClusters["cluster-1"] = time.Now().Add(-2 * removedKubernetesClustersTTL)
monitor.state.AddRemovedKubernetesCluster(models.RemovedKubernetesCluster{
ID: "cluster-1",
Name: "cluster",
RemovedAt: time.Now().Add(-2 * removedKubernetesClustersTTL),
})
monitor.cleanupRemovedKubernetesClusters(time.Now())
if len(monitor.removedKubernetesClusters) != 0 {
t.Fatal("expected removed clusters cleaned up")
}
if len(monitor.state.GetRemovedKubernetesClusters()) != 0 {
t.Fatal("expected state cleanup")
}
}
func TestApplyKubernetesReportUsesReceiptTimeForSkewedAgentClockLiveness(t *testing.T) {
monitor := newKubernetesTestMonitor()
agentClock := time.Now().UTC().Add(-3 * time.Hour)
before := time.Now().UTC()
cluster, err := monitor.ApplyKubernetesReport(agentsk8s.Report{
Cluster: agentsk8s.ClusterInfo{ID: "clock-skew-cluster", Name: "clock-skew"},
Agent: agentsk8s.AgentInfo{
ID: "k8s-clock-skew-agent",
Version: "6.0.3",
IntervalSeconds: 30,
},
Timestamp: agentClock,
}, nil)
after := time.Now().UTC()
if err != nil {
t.Fatalf("ApplyKubernetesReport: %v", err)
}
if cluster.LastSeen.Before(before) || cluster.LastSeen.After(after.Add(time.Second)) {
t.Fatalf("cluster LastSeen = %s, want server receipt between %s and %s", cluster.LastSeen, before, after)
}
if !cluster.LastSeen.After(agentClock.Add(2 * time.Hour)) {
t.Fatalf("cluster LastSeen followed skewed agent timestamp %s: got %s", agentClock, cluster.LastSeen)
}
}