Files
pulse/internal/monitoring/monitored_system_usage.go
T
rcourtman bd37ae186a Preserve live agent state over saved enrollment history
Saved enrollment records can correlate with a live Proxmox host after a
reinstall. The continuity overlay then replaced its current agent payload
with an older offline identity, hiding metrics and marking the node offline.

Use canonical matching to add only absent continuity resources. Preserve
current identity, telemetry and provider links without deleting history or
changing token admission. Cover repeated reads and identity collisions.

Refs #1913
2026-09-10 16:00:36 +01:00

200 lines
5.6 KiB
Go

package monitoring
import (
"strings"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
)
// MonitoredSystemUsageSnapshot describes whether the monitor can currently
// supply a canonical monitored-system count that is safe for settings and
// support context to consume.
type MonitoredSystemUsageSnapshot struct {
Count int
ReadState unifiedresources.ReadState
Available bool
UnavailableReason string
}
const (
MonitoredSystemUsageUnavailableMonitorState = "monitor_state_unavailable"
MonitoredSystemUsageUnavailableSupplementalInventoryUnsettled = "supplemental_inventory_unsettled"
MonitoredSystemUsageUnavailableSupplementalInventoryRebuildPending = "supplemental_inventory_rebuild_pending"
)
// MonitoredSystemUsage returns the canonical monitored-system count only when
// the current unified view is settled enough for settings/support context. When
// supplemental provider-owned sources are still settling, the result fails
// closed with Available=false.
func (m *Monitor) MonitoredSystemUsage() MonitoredSystemUsageSnapshot {
if m == nil {
return MonitoredSystemUsageSnapshot{UnavailableReason: MonitoredSystemUsageUnavailableMonitorState}
}
readState := m.GetUnifiedReadStateOrSnapshot()
if readState == nil {
return MonitoredSystemUsageSnapshot{UnavailableReason: MonitoredSystemUsageUnavailableMonitorState}
}
orgID := normalizedMonitorUsageOrgID(m)
readyAt, settled := m.supplementalInventoryReadyAt(orgID)
if !settled {
return MonitoredSystemUsageSnapshot{
ReadState: readState,
UnavailableReason: MonitoredSystemUsageUnavailableSupplementalInventoryUnsettled,
}
}
if !readyAt.IsZero() {
freshness := m.currentUnifiedResourceFreshness()
if freshness.IsZero() || freshness.Before(readyAt) {
return MonitoredSystemUsageSnapshot{
ReadState: readState,
UnavailableReason: MonitoredSystemUsageUnavailableSupplementalInventoryRebuildPending,
}
}
}
readState = m.readStateWithStandaloneHostContinuity(readState)
return MonitoredSystemUsageSnapshot{
Count: unifiedresources.MonitoredSystemCount(readState),
ReadState: readState,
Available: true,
}
}
func (m *Monitor) readStateWithStandaloneHostContinuity(
readState unifiedresources.ReadState,
) unifiedresources.ReadState {
if m == nil || readState == nil {
return readState
}
entries := m.recentStandaloneHostContinuityEntries()
if len(entries) == 0 {
return readState
}
liveHostIDs := make(map[string]struct{}, len(m.GetLiveHostsSnapshot()))
for _, host := range m.GetLiveHostsSnapshot() {
if id := strings.TrimSpace(host.ID); id != "" {
liveHostIDs[id] = struct{}{}
}
}
existingAgentIDs := make(map[string]struct{}, len(readState.Hosts()))
for _, host := range readState.Hosts() {
if host == nil {
continue
}
if agentID := strings.TrimSpace(host.AgentID()); agentID != "" {
existingAgentIDs[agentID] = struct{}{}
}
if resourceID := strings.TrimSpace(host.ID()); resourceID != "" {
existingAgentIDs[resourceID] = struct{}{}
}
}
records := make([]unifiedresources.IngestRecord, 0, len(entries))
for _, entry := range entries {
hostID := strings.TrimSpace(entry.HostID)
if hostID == "" {
continue
}
if _, ok := liveHostIDs[hostID]; ok {
continue
}
if _, ok := existingAgentIDs[hostID]; ok {
continue
}
// A continuity-only row has no live report in this monitor generation.
// Preserve its last-known identity so the operator can inspect or remove
// it, but never present persisted telemetry as a current online sighting.
host := hostFromContinuityEntry(entry)
host.Status = "offline"
records = append(records, unifiedresources.HostIngestRecord(host))
}
if len(records) == 0 {
return readState
}
return unifiedresources.ReadStateWithHostContinuity(readState, records)
}
func normalizedMonitorUsageOrgID(m *Monitor) string {
if m == nil {
return "default"
}
orgID := strings.TrimSpace(m.GetOrgID())
if orgID == "" {
return "default"
}
return orgID
}
func (m *Monitor) supplementalInventoryReadyAt(orgID string) (time.Time, bool) {
providers := m.supplementalProviderSnapshot()
if len(providers) == 0 {
return time.Time{}, true
}
var watermark time.Time
for _, provider := range providers {
if provider == nil {
continue
}
readinessProvider, hasReadiness := provider.(MonitorSupplementalInventoryReadinessProvider)
if !hasReadiness {
if len(supplementalProviderOwnedSourcesForOrg(provider, orgID)) > 0 {
return time.Time{}, false
}
continue
}
readyAt, settled := readinessProvider.SupplementalInventoryReadyAt(m, orgID)
if !settled {
return time.Time{}, false
}
if readyAt.After(watermark) {
watermark = readyAt
}
}
return watermark, true
}
func supplementalProviderOwnedSourcesForOrg(
provider MonitorSupplementalRecordsProvider,
orgID string,
) []unifiedresources.DataSource {
if provider == nil {
return nil
}
var sources []unifiedresources.DataSource
if tenantOwner, ok := provider.(interface {
SnapshotOwnedSourcesForOrg(string) []unifiedresources.DataSource
}); ok {
sources = tenantOwner.SnapshotOwnedSourcesForOrg(orgID)
} else if owner, ok := provider.(interface {
SnapshotOwnedSources() []unifiedresources.DataSource
}); ok {
sources = owner.SnapshotOwnedSources()
}
if len(sources) == 0 {
return nil
}
out := make([]unifiedresources.DataSource, 0, len(sources))
for _, source := range sources {
normalized := unifiedresources.DataSource(strings.ToLower(strings.TrimSpace(string(source))))
if normalized == "" {
continue
}
out = append(out, normalized)
}
return out
}