mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-27 05:57:27 +00:00
415c09dafc
The resources API rebuilds its registry from the monitor's unified seed, but built it with NewRegistry and plain ingests, so its stale pass used the registry defaults while the monitor judges each source by its configured polling cadence. A Proxmox node polled every two minutes read stale, with warning status, in /api/resources ninety seconds after its poll while the websocket called it online. Presentation coalescing had the same gap in both views. When it merges an agent row the registry kept apart into its Proxmox node by hostname, it picked each reading's source by the default thresholds, so the row showed a silent agent's readings over a poll the registry calls current. The seed now carries the thresholds its registry generation was judged by. The monitor adapter records them per generation: thresholds set since the last rebuild wait for the rebuild that applies them, and the read-state overlays and live supplemental refreshes of a generation are judged by its thresholds, since re-judging a listing cannot clear a stale verdict it already carries. The monitor lists a generation with its thresholds from one registry pointer, and a view built from mock fixtures or a bare snapshot reports none (the defaults it was judged by). The monitor hands seed, freshness and thresholds over from one view, per tenant too. The resources API builds its registry with them (NewRegistryWithStaleThresholds: ingests and stale passes that pass none use them) and keys its cache on its own copy. The coalesce judges metric freshness with metricSourceStale, the rule the stale pass and the merge already share, and both the broadcast and ListForPresentation pass their view's thresholds. The broadcast coalesce also honours the operator's merge exclusions, so the adapter's CoalesceForPresentation takes the listing's thresholds next to them. Patrol's scoped runtime state still rebuilds registries with the defaults.
172 lines
5.6 KiB
Go
172 lines
5.6 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/api/resourceapi"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
var errTenantMonitorUnavailable = errors.New("tenant monitor unavailable")
|
|
|
|
// getMonitor returns the tenant-specific monitor instance for the request.
|
|
// It uses the OrgID from the context (injected by TenantMiddleware).
|
|
// Non-default orgs fail closed when tenant monitor resolution is unavailable.
|
|
func (r *Router) getMonitor(req *http.Request) (*monitoring.Monitor, error) {
|
|
orgID := GetOrgID(req.Context())
|
|
if orgID == "" || orgID == "default" {
|
|
return r.monitor, nil
|
|
}
|
|
if r.mtMonitor == nil {
|
|
return nil, errTenantMonitorUnavailable
|
|
}
|
|
|
|
return r.mtMonitor.GetMonitor(orgID)
|
|
}
|
|
|
|
// MultiTenantStateProvider wraps a MultiTenantMonitor to provide state for specific tenants.
|
|
type MultiTenantStateProvider struct {
|
|
mtMonitor *monitoring.MultiTenantMonitor
|
|
defaultMonitor *monitoring.Monitor
|
|
}
|
|
|
|
// NewMultiTenantStateProvider creates a new tenant state provider.
|
|
func NewMultiTenantStateProvider(mtm *monitoring.MultiTenantMonitor, defaultM *monitoring.Monitor) *MultiTenantStateProvider {
|
|
return &MultiTenantStateProvider{
|
|
mtMonitor: mtm,
|
|
defaultMonitor: defaultM,
|
|
}
|
|
}
|
|
|
|
func (p *MultiTenantStateProvider) monitorForTenant(orgID string) *monitoring.Monitor {
|
|
if orgID == "" || orgID == "default" {
|
|
return p.defaultMonitor
|
|
}
|
|
|
|
if p.mtMonitor != nil {
|
|
monitor, err := p.mtMonitor.GetMonitor(orgID)
|
|
if err != nil {
|
|
log.Warn().Err(err).Str("org_id", orgID).Msg("Failed to get tenant monitor")
|
|
return nil
|
|
}
|
|
return monitor
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UnifiedReadStateForTenant returns the canonical typed unified read-state for a
|
|
// specific tenant, falling back to a snapshot-backed adapter only when the
|
|
// monitor has not been wired with a resource store yet.
|
|
func (p *MultiTenantStateProvider) UnifiedReadStateForTenant(orgID string) unifiedresources.ReadState {
|
|
monitor := p.monitorForTenant(orgID)
|
|
if monitor == nil {
|
|
return nil
|
|
}
|
|
return monitor.GetUnifiedReadStateOrSnapshot()
|
|
}
|
|
|
|
// UnifiedResourceSnapshotForTenant returns the canonical unified-resource seed
|
|
// for a specific tenant, along with its freshness marker.
|
|
func (p *MultiTenantStateProvider) UnifiedResourceSnapshotForTenant(orgID string) ([]unifiedresources.Resource, time.Time) {
|
|
monitor := p.monitorForTenant(orgID)
|
|
if monitor == nil {
|
|
return nil, time.Time{}
|
|
}
|
|
|
|
return monitor.UnifiedResourceSnapshot()
|
|
}
|
|
|
|
// The resources API reads both seeds-with-thresholds through optional
|
|
// interface checks, so a signature drift would silently restore the registry
|
|
// defaults.
|
|
var (
|
|
_ resourceapi.UnifiedResourceSnapshotWithStaleThresholdsProvider = (*monitoring.Monitor)(nil)
|
|
_ resourceapi.TenantUnifiedResourceSnapshotWithStaleThresholdsProvider = (*MultiTenantStateProvider)(nil)
|
|
)
|
|
|
|
// UnifiedResourceSnapshotWithStaleThresholdsForTenant returns a tenant's
|
|
// unified-resource seed with the stale thresholds its registry judged it by,
|
|
// read from one monitor in one call.
|
|
func (p *MultiTenantStateProvider) UnifiedResourceSnapshotWithStaleThresholdsForTenant(orgID string) ([]unifiedresources.Resource, time.Time, map[unifiedresources.DataSource]time.Duration) {
|
|
monitor := p.monitorForTenant(orgID)
|
|
if monitor == nil {
|
|
return nil, time.Time{}, nil
|
|
}
|
|
return monitor.UnifiedResourceSnapshotWithStaleThresholds()
|
|
}
|
|
|
|
// ResourceHealthAlertsForTenant returns only the minimal alert facts needed by
|
|
// the resource health projection, resolved from the same tenant monitor as the
|
|
// resource seed.
|
|
func (p *MultiTenantStateProvider) ResourceHealthAlertsForTenant(orgID string) []unifiedresources.ResourceHealthAlert {
|
|
monitor := p.monitorForTenant(orgID)
|
|
if monitor == nil {
|
|
return nil
|
|
}
|
|
active := monitor.ActiveAlertsSnapshot()
|
|
out := make([]unifiedresources.ResourceHealthAlert, 0, len(active))
|
|
for _, alert := range active {
|
|
out = append(out, unifiedresources.ResourceHealthAlert{
|
|
ResourceID: alert.ResourceID,
|
|
Level: alert.Level,
|
|
Type: alert.Type,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// SetMultiTenantMonitor updates the multi-tenant monitor manager.
|
|
// Used during reload.
|
|
func (r *Router) SetMultiTenantMonitor(mtm *monitoring.MultiTenantMonitor) {
|
|
r.mtMonitor = mtm
|
|
if r.configHandlers != nil {
|
|
r.configHandlers.SetMultiTenantMonitor(mtm)
|
|
r.configHandlers.SetConfig(r.config)
|
|
}
|
|
if r.alertHandlers != nil {
|
|
r.alertHandlers.SetMultiTenantMonitor(mtm)
|
|
}
|
|
if r.notificationHandlers != nil {
|
|
r.notificationHandlers.SetMultiTenantMonitor(mtm)
|
|
}
|
|
if r.dockerAgentHandlers != nil {
|
|
r.dockerAgentHandlers.SetMultiTenantMonitor(mtm)
|
|
}
|
|
if r.unifiedAgentHandlers != nil {
|
|
r.unifiedAgentHandlers.SetMultiTenantMonitor(mtm)
|
|
}
|
|
if r.kubernetesAgentHandlers != nil {
|
|
r.kubernetesAgentHandlers.SetMultiTenantMonitor(mtm)
|
|
}
|
|
if r.systemSettingsHandler != nil {
|
|
r.systemSettingsHandler.SetMultiTenantMonitor(mtm)
|
|
}
|
|
if r.aiSettingsHandler != nil {
|
|
r.aiSettingsHandler.SetMultiTenantMonitor(mtm)
|
|
}
|
|
if r.aiHandler != nil {
|
|
r.aiHandler.SetMultiTenantMonitor(mtm)
|
|
}
|
|
if mtm != nil {
|
|
if m, err := mtm.GetMonitor("default"); err == nil {
|
|
r.monitor = m
|
|
if r.notificationQueueHandlers != nil {
|
|
r.notificationQueueHandlers.SetMonitor(m)
|
|
}
|
|
r.bindDefaultMetadataStores(m)
|
|
}
|
|
mtm.SetMonitorInitializer(r.configureMonitorDependencies)
|
|
}
|
|
r.configureMetadataProviderFactory()
|
|
|
|
// Wire tenant state provider to resource handlers
|
|
if r.resourceHandlers != nil {
|
|
r.resourceHandlers.SetTenantStateProvider(NewMultiTenantStateProvider(mtm, r.monitor))
|
|
}
|
|
}
|