mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
The alert engine stamps metadata.resourceType on every alert, but the
websocket state path converts alerts.Alert to models.Alert, which had no
Metadata field, so every active alert reached the frontend stripped. The
history Type badge then fell back to unified-store lookups that miss
nodes (alert.resourceId is the platform-native node ID while unified
resources mint canonical ids, and alert.resourceName is the raw node
name while unified resources prefer the display name), rendering
Unknown. In mock mode the generated history rows had the same gap.
models.Alert gained the Metadata field in f62f35e24 (it rode along with
the memory-cache commit); this completes the transport:
- copy Metadata in activeAlertsSnapshot (websocket active alerts),
GetRecentlyResolved (resolved alerts to state), and the mock
UpdateAlertSnapshots conversion; sources are deep clones already
- deep-copy Metadata in models cloneAlert to keep the snapshot
clone contract honest
- stamp resourceType in the mock history generator using the real
engine vocabulary (node, vm, system-container)
- recognize system-container in the history Type badge map; that is
what the v6 engine stamps for LXC guests
Verified live in mock mode: history previously resolved 277 of 780
rows to Unknown (all node alerts); now 718/718 rows and 19/19 active
alerts carry resourceType and zero badges render Unknown.
366 lines
11 KiB
Go
366 lines
11 KiB
Go
package mock
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/alerts"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/truenas"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/vmware"
|
|
)
|
|
|
|
// FixtureGraph is the canonical mock runtime owner for snapshot-backed and
|
|
// provider-backed fixtures. All mock projections should derive from this graph
|
|
// rather than mixing independent snapshot and provider helpers.
|
|
type FixtureGraph struct {
|
|
State models.StateSnapshot
|
|
AlertHistory []models.Alert
|
|
PlatformFixtures PlatformFixtures
|
|
AvailabilityFixtures []AvailabilityFixture
|
|
DiscoveryFixtures []*DiscoveryFixture
|
|
}
|
|
|
|
func emptyFixtureGraph() FixtureGraph {
|
|
return FixtureGraph{
|
|
State: models.EmptyStateSnapshot(),
|
|
}
|
|
}
|
|
|
|
func buildFixtureGraph(cfg MockConfig, now time.Time) FixtureGraph {
|
|
setMockUpdateInterval(cfg.UpdateInterval)
|
|
graph := FixtureGraph{
|
|
State: buildFixtureState(cfg),
|
|
PlatformFixtures: defaultPlatformFixtures(),
|
|
AvailabilityFixtures: defaultAvailabilityFixtures(now),
|
|
}
|
|
applyDemoScenarioGraph(&graph, now)
|
|
syncMetricRoleRegistryFromGraph(graph)
|
|
graph.UpdateMetrics(cfg, now)
|
|
graph.AlertHistory = buildAlertHistory(graph.State.Nodes, graph.State.VMs, graph.State.Containers)
|
|
syncMetricRoleRegistryFromGraph(graph)
|
|
return graph
|
|
}
|
|
|
|
func cloneFixtureGraph(in FixtureGraph) FixtureGraph {
|
|
return FixtureGraph{
|
|
State: cloneState(in.State),
|
|
AlertHistory: append([]models.Alert(nil), in.AlertHistory...),
|
|
PlatformFixtures: clonePlatformFixtures(in.PlatformFixtures),
|
|
AvailabilityFixtures: cloneAvailabilityFixtures(in.AvailabilityFixtures),
|
|
DiscoveryFixtures: cloneDiscoveryFixtures(in.DiscoveryFixtures),
|
|
}
|
|
}
|
|
|
|
func (g *FixtureGraph) UpdateMetrics(cfg MockConfig, now time.Time) {
|
|
if g == nil {
|
|
return
|
|
}
|
|
|
|
setMockUpdateInterval(cfg.UpdateInterval)
|
|
applyDemoScenarioGraph(g, now)
|
|
syncMetricRoleRegistryFromGraph(*g)
|
|
updateFixtureStateMetricsAt(&g.State, cfg, now)
|
|
g.PlatformFixtures = rebasePlatformFixtures(g.PlatformFixtures, now)
|
|
g.AvailabilityFixtures = rebaseAvailabilityFixtures(g.AvailabilityFixtures, now)
|
|
applyDemoScenarioGraph(g, now)
|
|
g.DiscoveryFixtures = buildDiscoveryFixtures(g.State, now)
|
|
syncMetricRoleRegistryFromGraph(*g)
|
|
}
|
|
|
|
func (g *FixtureGraph) UpdateAlertSnapshots(active []alerts.Alert, resolved []models.ResolvedAlert) {
|
|
if g == nil {
|
|
return
|
|
}
|
|
|
|
converted := make([]models.Alert, 0, len(active))
|
|
for _, alert := range active {
|
|
converted = append(converted, models.Alert{
|
|
ID: alert.ID,
|
|
Type: alert.Type,
|
|
Level: string(alert.Level),
|
|
ResourceID: alert.ResourceID,
|
|
ResourceName: alert.ResourceName,
|
|
Node: alert.Node,
|
|
Instance: alert.Instance,
|
|
Message: alert.Message,
|
|
Value: alert.Value,
|
|
Threshold: alert.Threshold,
|
|
StartTime: alert.StartTime,
|
|
Acknowledged: alert.Acknowledged,
|
|
// GetActiveAlerts returns deep clones, so the map is already private.
|
|
Metadata: alert.Metadata,
|
|
})
|
|
}
|
|
|
|
g.State.ActiveAlerts = converted
|
|
g.State.RecentlyResolved = append([]models.ResolvedAlert(nil), resolved...)
|
|
}
|
|
|
|
func CurrentFixtureGraph() FixtureGraph {
|
|
if !IsMockEnabled() {
|
|
return emptyFixtureGraph()
|
|
}
|
|
|
|
dataMu.RLock()
|
|
defer dataMu.RUnlock()
|
|
|
|
return cloneFixtureGraph(mockGraph)
|
|
}
|
|
|
|
func currentOrDefaultPlatformFixtures() PlatformFixtures {
|
|
if !IsMockEnabled() {
|
|
return defaultPlatformFixtures()
|
|
}
|
|
|
|
return CurrentFixtureGraph().PlatformFixtures
|
|
}
|
|
|
|
func clonePlatformFixtures(in PlatformFixtures) PlatformFixtures {
|
|
return PlatformFixtures{
|
|
TrueNAS: cloneTrueNASFixtureSnapshot(in.TrueNAS),
|
|
VMware: cloneVMwareInventorySnapshot(in.VMware),
|
|
}
|
|
}
|
|
|
|
func cloneTrueNASFixtureSnapshot(in truenas.FixtureSnapshot) truenas.FixtureSnapshot {
|
|
out := in
|
|
out.System = cloneTrueNASSystemInfo(in.System)
|
|
out.Pools = append([]truenas.Pool(nil), in.Pools...)
|
|
out.Datasets = append([]truenas.Dataset(nil), in.Datasets...)
|
|
out.Disks = append([]truenas.Disk(nil), in.Disks...)
|
|
out.Alerts = append([]truenas.Alert(nil), in.Alerts...)
|
|
out.Apps = cloneTrueNASApps(in.Apps)
|
|
out.ZFSSnapshots = cloneTrueNASZFSSnapshots(in.ZFSSnapshots)
|
|
out.ReplicationTasks = cloneTrueNASReplicationTasks(in.ReplicationTasks)
|
|
return out
|
|
}
|
|
|
|
func cloneTrueNASSystemInfo(in truenas.SystemInfo) truenas.SystemInfo {
|
|
out := in
|
|
if len(in.TemperatureCelsius) > 0 {
|
|
out.TemperatureCelsius = make(map[string]float64, len(in.TemperatureCelsius))
|
|
for key, value := range in.TemperatureCelsius {
|
|
out.TemperatureCelsius[key] = value
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func cloneTrueNASApps(in []truenas.App) []truenas.App {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
|
|
out := make([]truenas.App, len(in))
|
|
for i := range in {
|
|
out[i] = in[i]
|
|
out[i].UsedHostIPs = append([]string(nil), in[i].UsedHostIPs...)
|
|
out[i].UsedPorts = cloneTrueNASAppPorts(in[i].UsedPorts)
|
|
out[i].Containers = cloneTrueNASAppContainers(in[i].Containers)
|
|
out[i].Volumes = append([]truenas.AppVolume(nil), in[i].Volumes...)
|
|
out[i].Images = append([]string(nil), in[i].Images...)
|
|
out[i].Networks = cloneTrueNASAppNetworks(in[i].Networks)
|
|
out[i].Stats = cloneTrueNASAppStats(in[i].Stats)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func cloneTrueNASAppPorts(in []truenas.AppPort) []truenas.AppPort {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
|
|
out := make([]truenas.AppPort, len(in))
|
|
for i := range in {
|
|
out[i] = in[i]
|
|
out[i].HostPorts = append([]truenas.AppHostPort(nil), in[i].HostPorts...)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func cloneTrueNASAppContainers(in []truenas.AppContainer) []truenas.AppContainer {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
|
|
out := make([]truenas.AppContainer, len(in))
|
|
for i := range in {
|
|
out[i] = in[i]
|
|
out[i].PortConfig = cloneTrueNASAppPorts(in[i].PortConfig)
|
|
out[i].VolumeMounts = append([]truenas.AppVolume(nil), in[i].VolumeMounts...)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func cloneTrueNASAppNetworks(in []truenas.AppNetwork) []truenas.AppNetwork {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
|
|
out := make([]truenas.AppNetwork, len(in))
|
|
for i := range in {
|
|
out[i] = in[i]
|
|
out[i].Labels = cloneStringMap(in[i].Labels)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func cloneTrueNASAppStats(in *truenas.AppStats) *truenas.AppStats {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
|
|
out := *in
|
|
out.Interfaces = append([]truenas.AppInterfaceStats(nil), in.Interfaces...)
|
|
return &out
|
|
}
|
|
|
|
func cloneTrueNASZFSSnapshots(in []truenas.ZFSSnapshot) []truenas.ZFSSnapshot {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
|
|
out := make([]truenas.ZFSSnapshot, len(in))
|
|
for i := range in {
|
|
out[i] = in[i]
|
|
out[i].CreatedAt = cloneTimePtr(in[i].CreatedAt)
|
|
out[i].UsedBytes = cloneInt64Ptr(in[i].UsedBytes)
|
|
out[i].Referenced = cloneInt64Ptr(in[i].Referenced)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func cloneTrueNASReplicationTasks(in []truenas.ReplicationTask) []truenas.ReplicationTask {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
|
|
out := make([]truenas.ReplicationTask, len(in))
|
|
for i := range in {
|
|
out[i] = in[i]
|
|
out[i].SourceDatasets = append([]string(nil), in[i].SourceDatasets...)
|
|
out[i].LastRun = cloneTimePtr(in[i].LastRun)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func cloneVMwareInventorySnapshot(in vmware.InventorySnapshot) vmware.InventorySnapshot {
|
|
out := in
|
|
out.Hosts = cloneVMwareInventoryHosts(in.Hosts)
|
|
out.VMs = cloneVMwareInventoryVMs(in.VMs)
|
|
out.Datastores = cloneVMwareInventoryDatastores(in.Datastores)
|
|
out.Networks = cloneVMwareInventoryNetworks(in.Networks)
|
|
out.EnrichmentIssues = append([]vmware.InventoryEnrichmentIssue(nil), in.EnrichmentIssues...)
|
|
return out
|
|
}
|
|
|
|
func cloneVMwareInventoryHosts(in []vmware.InventoryHost) []vmware.InventoryHost {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
|
|
out := make([]vmware.InventoryHost, len(in))
|
|
for i := range in {
|
|
out[i] = in[i]
|
|
out[i].DatastoreIDs = append([]string(nil), in[i].DatastoreIDs...)
|
|
out[i].DatastoreNames = append([]string(nil), in[i].DatastoreNames...)
|
|
out[i].TriggeredAlarms = append([]vmware.InventoryAlarm(nil), in[i].TriggeredAlarms...)
|
|
out[i].RecentTasks = append([]vmware.InventoryTask(nil), in[i].RecentTasks...)
|
|
out[i].RecentEvents = append([]vmware.InventoryEvent(nil), in[i].RecentEvents...)
|
|
out[i].Metrics = cloneVMwareInventoryMetrics(in[i].Metrics)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func cloneVMwareInventoryVMs(in []vmware.InventoryVM) []vmware.InventoryVM {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
|
|
out := make([]vmware.InventoryVM, len(in))
|
|
for i := range in {
|
|
out[i] = in[i]
|
|
out[i].DatastoreIDs = append([]string(nil), in[i].DatastoreIDs...)
|
|
out[i].DatastoreNames = append([]string(nil), in[i].DatastoreNames...)
|
|
out[i].GuestIPAddresses = append([]string(nil), in[i].GuestIPAddresses...)
|
|
out[i].TriggeredAlarms = append([]vmware.InventoryAlarm(nil), in[i].TriggeredAlarms...)
|
|
out[i].RecentTasks = append([]vmware.InventoryTask(nil), in[i].RecentTasks...)
|
|
out[i].RecentEvents = append([]vmware.InventoryEvent(nil), in[i].RecentEvents...)
|
|
out[i].Metrics = cloneVMwareInventoryMetrics(in[i].Metrics)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func cloneVMwareInventoryDatastores(in []vmware.InventoryDatastore) []vmware.InventoryDatastore {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
|
|
out := make([]vmware.InventoryDatastore, len(in))
|
|
for i := range in {
|
|
out[i] = in[i]
|
|
out[i].HostIDs = append([]string(nil), in[i].HostIDs...)
|
|
out[i].HostNames = append([]string(nil), in[i].HostNames...)
|
|
out[i].VMIDs = append([]string(nil), in[i].VMIDs...)
|
|
out[i].VMNames = append([]string(nil), in[i].VMNames...)
|
|
out[i].Accessible = cloneBoolPtr(in[i].Accessible)
|
|
out[i].MultipleHostAccess = cloneBoolPtr(in[i].MultipleHostAccess)
|
|
out[i].TriggeredAlarms = append([]vmware.InventoryAlarm(nil), in[i].TriggeredAlarms...)
|
|
out[i].RecentTasks = append([]vmware.InventoryTask(nil), in[i].RecentTasks...)
|
|
out[i].RecentEvents = append([]vmware.InventoryEvent(nil), in[i].RecentEvents...)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func cloneVMwareInventoryNetworks(in []vmware.InventoryNetwork) []vmware.InventoryNetwork {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
|
|
out := make([]vmware.InventoryNetwork, len(in))
|
|
for i := range in {
|
|
out[i] = in[i]
|
|
out[i].HostIDs = append([]string(nil), in[i].HostIDs...)
|
|
out[i].HostNames = append([]string(nil), in[i].HostNames...)
|
|
out[i].VMIDs = append([]string(nil), in[i].VMIDs...)
|
|
out[i].VMNames = append([]string(nil), in[i].VMNames...)
|
|
out[i].TriggeredAlarms = append([]vmware.InventoryAlarm(nil), in[i].TriggeredAlarms...)
|
|
out[i].RecentTasks = append([]vmware.InventoryTask(nil), in[i].RecentTasks...)
|
|
out[i].RecentEvents = append([]vmware.InventoryEvent(nil), in[i].RecentEvents...)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func cloneVMwareInventoryMetrics(in *vmware.InventoryMetrics) *vmware.InventoryMetrics {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
|
|
out := *in
|
|
out.CPUPercent = cloneFloat64Ptr(in.CPUPercent)
|
|
out.MemoryPercent = cloneFloat64Ptr(in.MemoryPercent)
|
|
out.MemoryUsedBytes = cloneInt64Ptr(in.MemoryUsedBytes)
|
|
out.MemoryTotalBytes = cloneInt64Ptr(in.MemoryTotalBytes)
|
|
out.NetInBytesPerSecond = cloneFloat64Ptr(in.NetInBytesPerSecond)
|
|
out.NetOutBytesPerSecond = cloneFloat64Ptr(in.NetOutBytesPerSecond)
|
|
out.DiskReadBytesPerSecond = cloneFloat64Ptr(in.DiskReadBytesPerSecond)
|
|
out.DiskWriteBytesPerSecond = cloneFloat64Ptr(in.DiskWriteBytesPerSecond)
|
|
out.UptimeSeconds = cloneInt64Ptr(in.UptimeSeconds)
|
|
out.DiskUsedBytes = cloneInt64Ptr(in.DiskUsedBytes)
|
|
out.DiskTotalBytes = cloneInt64Ptr(in.DiskTotalBytes)
|
|
out.DiskPercent = cloneFloat64Ptr(in.DiskPercent)
|
|
return &out
|
|
}
|
|
|
|
func cloneFloat64Ptr(n *float64) *float64 {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
value := *n
|
|
return &value
|
|
}
|