Files
pulse/internal/monitoring/poll_providers.go
T
rcourtman c70431caaf Honor configured availability poll interval in the scheduler
An availability target's configured poll interval only seeded the
adaptive scheduler: BuildPlan derived every instance's cadence from the
global adaptive bounds, and a failing probe raised the staleness score
and error penalty, collapsing the probe interval toward the global
5-second minimum. With interval 120s and failure threshold 4 the alert
was promised after ~8 minutes of downtime but fired within the first
minute because the four consecutive failures accumulated at the
collapsed cadence (#1582).

Availability checks promise pollInterval x failureThreshold as the
detection window, so the cadence is a user contract, not a scheduling
hint. Add a FixedIntervalPollProvider extension that pins an instance
to its configured interval, implement it for availability targets, and
bypass adaptive selection wherever the next run is computed (plan
building, rescheduling, and the non-adaptive fallback paths).
2026-07-16 09:26:53 +01:00

1033 lines
27 KiB
Go

package monitoring
import (
"fmt"
"sort"
"strings"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
"github.com/rcourtman/pulse-go-rewrite/pkg/pbs"
"github.com/rcourtman/pulse-go-rewrite/pkg/pmg"
)
// PollProvider defines how a monitoring backend participates in scheduling and
// task execution.
type PollProvider interface {
Type() InstanceType
ListInstances(m *Monitor) []string
BaseInterval(m *Monitor) time.Duration
BuildPollTask(m *Monitor, instanceName string) (PollTask, error)
}
// PollProviderInstanceInfo describes a provider instance for health/status APIs.
type PollProviderInstanceInfo struct {
Name string
DisplayName string
Connection string
Metadata map[string]string
}
// InstanceInfoPollProvider is an optional PollProvider extension for providers
// that expose instance metadata for scheduler health responses.
type InstanceInfoPollProvider interface {
DescribeInstances(m *Monitor) []PollProviderInstanceInfo
}
// ConnectionStatusPollProvider is an optional PollProvider extension that
// publishes connection status entries keyed by external node ID (for example:
// "pve-node-a", "pbs-backup-1", "xcp-cluster-1").
type ConnectionStatusPollProvider interface {
ConnectionStatuses(m *Monitor) map[string]bool
}
// ConnectionHealthKeyPollProvider is an optional PollProvider extension for
// provider-owned connection health key normalization in monitor state.
type ConnectionHealthKeyPollProvider interface {
ConnectionHealthKey(m *Monitor, instanceName string) string
}
// FixedIntervalPollProvider is an optional PollProvider extension for providers
// whose instances carry a user-configured poll cadence that must be honored
// exactly, bypassing adaptive interval selection.
type FixedIntervalPollProvider interface {
FixedInstanceInterval(m *Monitor, instanceName string) time.Duration
}
// SupplementalRecordsPollProvider is an optional PollProvider extension for
// providers that can emit source-native unified ingest records.
type SupplementalRecordsPollProvider interface {
SupplementalSource() unifiedresources.DataSource
SupplementalRecords(m *Monitor, orgID string) []unifiedresources.IngestRecord
}
// SupplementalChangesPollProvider is an optional PollProvider extension for
// providers that can emit canonical timeline changes alongside supplemental
// source-native records.
type SupplementalChangesPollProvider interface {
SupplementalSource() unifiedresources.DataSource
SupplementalChanges(m *Monitor, orgID string) []unifiedresources.ResourceChange
}
// SnapshotOwnedSourcesPollProvider is an optional SupplementalRecordsPollProvider
// extension for providers that fully own source-native resource ingest.
// When set, legacy snapshot slices for these sources are suppressed.
type SnapshotOwnedSourcesPollProvider interface {
SnapshotOwnedSources(m *Monitor) []unifiedresources.DataSource
}
type pollProviderAdapter struct {
instanceType InstanceType
listInstances func(*Monitor) []string
describeInstances func(*Monitor) []PollProviderInstanceInfo
connectionStatus func(*Monitor) map[string]bool
connectionKey func(*Monitor, string) string
baseInterval func(*Monitor) time.Duration
buildPollTask func(*Monitor, string) (PollTask, error)
}
func (p pollProviderAdapter) Type() InstanceType {
return p.instanceType
}
func (p pollProviderAdapter) ListInstances(m *Monitor) []string {
if p.listInstances == nil {
return nil
}
return p.listInstances(m)
}
func (p pollProviderAdapter) DescribeInstances(m *Monitor) []PollProviderInstanceInfo {
if p.describeInstances == nil {
return nil
}
return p.describeInstances(m)
}
func (p pollProviderAdapter) ConnectionStatuses(m *Monitor) map[string]bool {
if p.connectionStatus == nil {
return nil
}
return p.connectionStatus(m)
}
func (p pollProviderAdapter) ConnectionHealthKey(m *Monitor, instanceName string) string {
if p.connectionKey == nil {
return ""
}
return p.connectionKey(m, instanceName)
}
func (p pollProviderAdapter) BaseInterval(m *Monitor) time.Duration {
if p.baseInterval == nil {
return 0
}
return p.baseInterval(m)
}
func (p pollProviderAdapter) BuildPollTask(m *Monitor, instanceName string) (PollTask, error) {
if p.buildPollTask == nil {
return PollTask{}, fmt.Errorf("provider %q does not support poll task construction", p.instanceType)
}
return p.buildPollTask(m, instanceName)
}
// pollProviderInstanceConfig is one configured instance entry (name + host)
// independent of the per-platform config struct type.
type pollProviderInstanceConfig struct {
Name string
Host string
}
// sortedClientNames returns the sorted key set of a provider client map read
// under the monitor lock.
func sortedClientNames[C any](m *Monitor, clientsOf func(*Monitor) map[string]C) []string {
if m == nil {
return nil
}
m.mu.RLock()
clients := clientsOf(m)
names := make([]string, 0, len(clients))
for name := range clients {
names = append(names, name)
}
m.mu.RUnlock()
sort.Strings(names)
return names
}
// describeProviderInstances merges configured instances with live client map
// entries into the sorted instance info list shared by the PVE/PBS/PMG
// providers. configInstances and clientsOf are invoked under the monitor lock.
func describeProviderInstances[C any](
m *Monitor,
fallbackName string,
configInstances func(*Monitor) []pollProviderInstanceConfig,
clientsOf func(*Monitor) map[string]C,
) []PollProviderInstanceInfo {
if m == nil {
return nil
}
byName := make(map[string]PollProviderInstanceInfo)
m.mu.RLock()
for _, inst := range configInstances(m) {
name := strings.TrimSpace(inst.Name)
if name == "" {
name = strings.TrimSpace(inst.Host)
}
if name == "" {
name = fallbackName
}
display := strings.TrimSpace(inst.Name)
if display == "" {
display = name
}
connection := strings.TrimSpace(inst.Host)
byName[name] = PollProviderInstanceInfo{
Name: name,
DisplayName: display,
Connection: connection,
}
}
for name := range clientsOf(m) {
trimmed := strings.TrimSpace(name)
if trimmed == "" {
continue
}
if _, exists := byName[trimmed]; exists {
continue
}
byName[trimmed] = PollProviderInstanceInfo{Name: trimmed, DisplayName: trimmed}
}
m.mu.RUnlock()
if len(byName) == 0 {
return nil
}
names := make([]string, 0, len(byName))
for name := range byName {
names = append(names, name)
}
sort.Strings(names)
infos := make([]PollProviderInstanceInfo, 0, len(names))
for _, name := range names {
infos = append(infos, byName[name])
}
return infos
}
// providerConnectionStatuses publishes per-instance connection booleans keyed
// by "<keyPrefix><name>" for configured instances, shared by the PVE/PBS/PMG
// providers. configInstances and clientsOf are invoked under the monitor lock.
func providerConnectionStatuses[C comparable](
m *Monitor,
instanceType InstanceType,
keyPrefix string,
configInstances func(*Monitor) []pollProviderInstanceConfig,
clientsOf func(*Monitor) map[string]C,
) map[string]bool {
if m == nil {
return nil
}
var zero C
statuses := make(map[string]bool)
m.mu.RLock()
clients := clientsOf(m)
for _, inst := range configInstances(m) {
name := strings.TrimSpace(inst.Name)
if name == "" {
name = strings.TrimSpace(inst.Host)
}
if name == "" {
continue
}
key := keyPrefix + name
connected := false
if client, exists := clients[name]; exists && client != zero {
if m.state != nil && m.state.ConnectionHealth != nil {
stateKey := m.connectionHealthStateKey(instanceType, name)
connected = m.state.ConnectionHealth[stateKey]
} else {
connected = true
}
}
statuses[key] = connected
}
m.mu.RUnlock()
if len(statuses) == 0 {
return nil
}
return statuses
}
func pveInstanceConfigs(m *Monitor) []pollProviderInstanceConfig {
if m.config == nil {
return nil
}
out := make([]pollProviderInstanceConfig, 0, len(m.config.PVEInstances))
for _, inst := range m.config.PVEInstances {
out = append(out, pollProviderInstanceConfig{Name: inst.Name, Host: inst.Host})
}
return out
}
func pbsInstanceConfigs(m *Monitor) []pollProviderInstanceConfig {
if m.config == nil {
return nil
}
out := make([]pollProviderInstanceConfig, 0, len(m.config.PBSInstances))
for _, inst := range m.config.PBSInstances {
out = append(out, pollProviderInstanceConfig{Name: inst.Name, Host: inst.Host})
}
return out
}
func pmgInstanceConfigs(m *Monitor) []pollProviderInstanceConfig {
if m.config == nil {
return nil
}
out := make([]pollProviderInstanceConfig, 0, len(m.config.PMGInstances))
for _, inst := range m.config.PMGInstances {
out = append(out, pollProviderInstanceConfig{Name: inst.Name, Host: inst.Host})
}
return out
}
func pveClientMap(m *Monitor) map[string]PVEClientInterface { return m.pveClients }
func pbsClientMap(m *Monitor) map[string]*pbs.Client { return m.pbsClients }
func pmgClientMap(m *Monitor) map[string]*pmg.Client { return m.pmgClients }
func newPVEPollProvider() PollProvider {
return pollProviderAdapter{
instanceType: InstanceTypePVE,
listInstances: func(m *Monitor) []string {
return sortedClientNames(m, pveClientMap)
},
describeInstances: func(m *Monitor) []PollProviderInstanceInfo {
return describeProviderInstances(m, "pve-instance", pveInstanceConfigs, pveClientMap)
},
connectionStatus: func(m *Monitor) map[string]bool {
return providerConnectionStatuses(m, InstanceTypePVE, "pve-", pveInstanceConfigs, pveClientMap)
},
connectionKey: func(_ *Monitor, instanceName string) string {
return strings.TrimSpace(instanceName)
},
baseInterval: func(m *Monitor) time.Duration {
if m == nil {
return 0
}
return m.effectivePVEPollingInterval()
},
buildPollTask: func(m *Monitor, instanceName string) (PollTask, error) {
if m == nil {
return PollTask{}, fmt.Errorf("monitor is nil")
}
client, ok := m.getPVEClient(instanceName)
if !ok || client == nil {
return PollTask{}, fmt.Errorf("PVE client missing for scheduled task")
}
return PollTask{
InstanceName: instanceName,
InstanceType: string(InstanceTypePVE),
PVEClient: client,
}, nil
},
}
}
// prefixedPollProviderSpec captures the bits that differ between the PBS and
// PMG providers, which otherwise share the full adapter wiring. prefix
// ("pbs-", "pmg-") namespaces connection status keys and connection health
// keys, and derives the "<prefix>instance" fallback display name.
type prefixedPollProviderSpec[C comparable] struct {
instanceType InstanceType
prefix string
configInstances func(*Monitor) []pollProviderInstanceConfig
clients func(*Monitor) map[string]C
pollingInterval func(*config.Config) time.Duration
buildPollTask func(*Monitor, string) (PollTask, error)
}
func newPrefixedPollProvider[C comparable](spec prefixedPollProviderSpec[C]) PollProvider {
return pollProviderAdapter{
instanceType: spec.instanceType,
listInstances: func(m *Monitor) []string {
return sortedClientNames(m, spec.clients)
},
describeInstances: func(m *Monitor) []PollProviderInstanceInfo {
return describeProviderInstances(m, spec.prefix+"instance", spec.configInstances, spec.clients)
},
connectionStatus: func(m *Monitor) map[string]bool {
return providerConnectionStatuses(m, spec.instanceType, spec.prefix, spec.configInstances, spec.clients)
},
connectionKey: func(_ *Monitor, instanceName string) string {
trimmed := strings.TrimSpace(instanceName)
if trimmed == "" {
return ""
}
return spec.prefix + trimmed
},
baseInterval: func(m *Monitor) time.Duration {
if m == nil || m.config == nil {
return 0
}
return clampInterval(spec.pollingInterval(m.config), 10*time.Second, time.Hour)
},
buildPollTask: spec.buildPollTask,
}
}
func newPBSPollProvider() PollProvider {
return newPrefixedPollProvider(prefixedPollProviderSpec[*pbs.Client]{
instanceType: InstanceTypePBS,
prefix: "pbs-",
configInstances: pbsInstanceConfigs,
clients: pbsClientMap,
pollingInterval: func(cfg *config.Config) time.Duration { return cfg.PBSPollingInterval },
buildPollTask: func(m *Monitor, instanceName string) (PollTask, error) {
if m == nil {
return PollTask{}, fmt.Errorf("monitor is nil")
}
client, ok := m.getPBSClient(instanceName)
if !ok || client == nil {
return PollTask{}, fmt.Errorf("PBS client missing for scheduled task")
}
return PollTask{
InstanceName: instanceName,
InstanceType: string(InstanceTypePBS),
PBSClient: client,
}, nil
},
})
}
func newPMGPollProvider() PollProvider {
return newPrefixedPollProvider(prefixedPollProviderSpec[*pmg.Client]{
instanceType: InstanceTypePMG,
prefix: "pmg-",
configInstances: pmgInstanceConfigs,
clients: pmgClientMap,
pollingInterval: func(cfg *config.Config) time.Duration { return cfg.PMGPollingInterval },
buildPollTask: func(m *Monitor, instanceName string) (PollTask, error) {
if m == nil {
return PollTask{}, fmt.Errorf("monitor is nil")
}
client, ok := m.getPMGClient(instanceName)
if !ok || client == nil {
return PollTask{}, fmt.Errorf("PMG client missing for scheduled task")
}
return PollTask{
InstanceName: instanceName,
InstanceType: string(InstanceTypePMG),
PMGClient: client,
}, nil
},
})
}
// RegisterPollProvider registers or replaces a polling provider for an
// instance type.
func (m *Monitor) RegisterPollProvider(provider PollProvider) error {
if m == nil {
return fmt.Errorf("monitor is nil")
}
if provider == nil {
return fmt.Errorf("poll provider is nil")
}
providerType := InstanceType(strings.TrimSpace(string(provider.Type())))
if providerType == "" {
return fmt.Errorf("poll provider type is required")
}
m.mu.Lock()
if m.pollProviders == nil {
m.pollProviders = make(map[InstanceType]PollProvider)
}
m.pollProviders[providerType] = provider
m.mu.Unlock()
m.refreshInstanceInfoCacheFromProviders()
return nil
}
func (m *Monitor) registerBuiltInPollProviders() {
_ = m.RegisterPollProvider(newPVEPollProvider())
_ = m.RegisterPollProvider(newPBSPollProvider())
_ = m.RegisterPollProvider(newPMGPollProvider())
_ = m.RegisterPollProvider(newAvailabilityPollProvider())
}
func (m *Monitor) pollProviderSnapshot() []PollProvider {
if m == nil {
return nil
}
m.mu.RLock()
if len(m.pollProviders) == 0 {
m.mu.RUnlock()
return nil
}
byType := make(map[InstanceType]PollProvider, len(m.pollProviders))
for providerType, provider := range m.pollProviders {
if provider == nil {
continue
}
if providerType == "" {
providerType = provider.Type()
}
if providerType == "" {
continue
}
byType[providerType] = provider
}
m.mu.RUnlock()
if len(byType) == 0 {
return nil
}
types := make([]string, 0, len(byType))
for providerType := range byType {
types = append(types, string(providerType))
}
sort.Strings(types)
providers := make([]PollProvider, 0, len(types))
for _, providerType := range types {
providers = append(providers, byType[InstanceType(providerType)])
}
return providers
}
func (m *Monitor) pollProviderSnapshotWithBuiltins() []PollProvider {
byType := make(map[InstanceType]PollProvider)
for _, provider := range m.pollProviderSnapshot() {
if provider == nil {
continue
}
providerType := provider.Type()
if providerType == "" {
continue
}
byType[providerType] = provider
}
builtins := []PollProvider{
newPVEPollProvider(),
newPBSPollProvider(),
newPMGPollProvider(),
newAvailabilityPollProvider(),
}
for _, provider := range builtins {
if provider == nil {
continue
}
providerType := provider.Type()
if providerType == "" {
continue
}
if _, exists := byType[providerType]; !exists {
byType[providerType] = provider
}
}
if len(byType) == 0 {
return nil
}
types := make([]string, 0, len(byType))
for providerType := range byType {
types = append(types, string(providerType))
}
sort.Strings(types)
providers := make([]PollProvider, 0, len(types))
for _, providerType := range types {
providers = append(providers, byType[InstanceType(providerType)])
}
return providers
}
func (m *Monitor) getPollProvider(instanceType InstanceType) PollProvider {
if m == nil {
return nil
}
m.mu.RLock()
provider := m.pollProviders[instanceType]
m.mu.RUnlock()
if provider != nil {
return provider
}
switch instanceType {
case InstanceTypePVE:
return newPVEPollProvider()
case InstanceTypePBS:
return newPBSPollProvider()
case InstanceTypePMG:
return newPMGPollProvider()
case InstanceTypeAvailability:
return newAvailabilityPollProvider()
default:
return nil
}
}
func (m *Monitor) activeSchedulerKeys() map[string]struct{} {
activeKeys := make(map[string]struct{})
for _, provider := range m.pollProviderSnapshotWithBuiltins() {
if provider == nil {
continue
}
instanceType := provider.Type()
for _, instanceName := range provider.ListInstances(m) {
name := strings.TrimSpace(instanceName)
if name == "" {
continue
}
activeKeys[schedulerKey(instanceType, name)] = struct{}{}
}
}
return activeKeys
}
func cloneProviderMetadata(in map[string]string) map[string]string {
if len(in) == 0 {
return nil
}
out := make(map[string]string, len(in))
for k, v := range in {
out[k] = v
}
return out
}
func (m *Monitor) providerInstanceInfos() map[string]*instanceInfo {
if m == nil {
return nil
}
infos := make(map[string]*instanceInfo)
for _, provider := range m.pollProviderSnapshotWithBuiltins() {
if provider == nil {
continue
}
providerType := provider.Type()
if strings.TrimSpace(string(providerType)) == "" {
continue
}
addInfo := func(name, display, connection string, metadata map[string]string) {
name = strings.TrimSpace(name)
if name == "" {
return
}
key := schedulerKey(providerType, name)
display = strings.TrimSpace(display)
connection = strings.TrimSpace(connection)
if display == "" {
display = name
}
existing, ok := infos[key]
if !ok || existing == nil {
infos[key] = &instanceInfo{
Key: key,
Type: providerType,
DisplayName: display,
Connection: connection,
Metadata: cloneProviderMetadata(metadata),
}
return
}
if existing.Type == "" {
existing.Type = providerType
}
if existing.DisplayName == "" && display != "" {
existing.DisplayName = display
}
if existing.Connection == "" && connection != "" {
existing.Connection = connection
}
if len(metadata) == 0 {
return
}
if existing.Metadata == nil {
existing.Metadata = make(map[string]string, len(metadata))
}
for mk, mv := range metadata {
existing.Metadata[mk] = mv
}
}
if describer, ok := provider.(InstanceInfoPollProvider); ok {
for _, info := range describer.DescribeInstances(m) {
addInfo(info.Name, info.DisplayName, info.Connection, info.Metadata)
}
}
for _, name := range provider.ListInstances(m) {
addInfo(name, name, "", nil)
}
}
if len(infos) == 0 {
return nil
}
return infos
}
func (m *Monitor) refreshInstanceInfoCacheFromProviders() {
if m == nil {
return
}
infos := m.providerInstanceInfos()
m.mu.Lock()
if infos == nil {
m.instanceInfoCache = make(map[string]*instanceInfo)
} else {
m.instanceInfoCache = infos
}
m.mu.Unlock()
}
func (m *Monitor) connectionHealthStateKey(instanceType InstanceType, instanceName string) string {
trimmedName := strings.TrimSpace(instanceName)
if trimmedName == "" {
return ""
}
if provider := m.getPollProvider(instanceType); provider != nil {
if keyProvider, ok := provider.(ConnectionHealthKeyPollProvider); ok {
if key := strings.TrimSpace(keyProvider.ConnectionHealthKey(m, trimmedName)); key != "" {
return key
}
}
}
trimmedType := strings.TrimSpace(string(instanceType))
if trimmedType == "" {
return trimmedName
}
return trimmedType + "-" + trimmedName
}
func (m *Monitor) setProviderConnectionHealth(instanceType InstanceType, instanceName string, healthy bool) {
if m == nil || m.state == nil {
return
}
key := m.connectionHealthStateKey(instanceType, instanceName)
if key == "" {
return
}
m.state.SetConnectionHealth(key, healthy)
}
func (m *Monitor) removeProviderConnectionHealth(instanceType InstanceType, instanceName string) {
if m == nil || m.state == nil {
return
}
key := m.connectionHealthStateKey(instanceType, instanceName)
if key == "" {
return
}
m.state.RemoveConnectionHealth(key)
}
func (m *Monitor) providerConnectionStatuses(provider PollProvider) map[string]bool {
if m == nil || provider == nil {
return nil
}
if statusProvider, ok := provider.(ConnectionStatusPollProvider); ok {
statuses := statusProvider.ConnectionStatuses(m)
out := make(map[string]bool, len(statuses))
for key, connected := range statuses {
trimmed := strings.TrimSpace(key)
if trimmed == "" {
continue
}
out[trimmed] = connected
}
if len(out) > 0 {
return out
}
}
instanceType := strings.TrimSpace(string(provider.Type()))
if instanceType == "" {
return nil
}
names := provider.ListInstances(m)
if len(names) == 0 {
return nil
}
connectionHealth := make(map[string]bool)
m.mu.RLock()
if m.state != nil && m.state.ConnectionHealth != nil {
connectionHealth = make(map[string]bool, len(m.state.ConnectionHealth))
for key, healthy := range m.state.ConnectionHealth {
connectionHealth[key] = healthy
}
}
m.mu.RUnlock()
statuses := make(map[string]bool, len(names))
for _, name := range names {
trimmedName := strings.TrimSpace(name)
if trimmedName == "" {
continue
}
key := instanceType + "-" + trimmedName
stateKey := m.connectionHealthStateKey(InstanceType(instanceType), trimmedName)
if stateKey == "" {
stateKey = trimmedName
}
connected := false
if healthy, exists := connectionHealth[stateKey]; exists {
connected = healthy
} else if healthy, exists := connectionHealth[key]; exists {
connected = healthy
} else if healthy, exists := connectionHealth[trimmedName]; exists {
connected = healthy
}
statuses[key] = connected
}
if len(statuses) == 0 {
return nil
}
return statuses
}
func (m *Monitor) providerOwnedSnapshotSources() []unifiedresources.DataSource {
owned := make(map[string]unifiedresources.DataSource)
providers := m.pollProviderSnapshotWithBuiltins()
for _, provider := range providers {
if provider == nil {
continue
}
// Snapshot source ownership is only meaningful for providers that can
// emit source-native records.
if _, ok := provider.(SupplementalRecordsPollProvider); !ok {
continue
}
owner, ok := provider.(SnapshotOwnedSourcesPollProvider)
if !ok {
continue
}
for _, source := range owner.SnapshotOwnedSources(m) {
key := strings.ToLower(strings.TrimSpace(string(source)))
if key == "" {
continue
}
owned[key] = unifiedresources.DataSource(key)
}
}
orgID := "default"
if m != nil {
if trimmed := strings.TrimSpace(m.orgID); trimmed != "" {
orgID = trimmed
}
}
for _, provider := range m.supplementalProviderSnapshot() {
if provider == nil {
continue
}
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()
}
for _, source := range sources {
key := strings.ToLower(strings.TrimSpace(string(source)))
if key == "" {
continue
}
owned[key] = unifiedresources.DataSource(key)
}
}
if len(owned) == 0 {
return nil
}
keys := make([]string, 0, len(owned))
for key := range owned {
keys = append(keys, key)
}
sort.Strings(keys)
sources := make([]unifiedresources.DataSource, 0, len(keys))
for _, key := range keys {
sources = append(sources, owned[key])
}
return sources
}
func (m *Monitor) supplementalProviderSnapshot() map[unifiedresources.DataSource]MonitorSupplementalRecordsProvider {
if m == nil {
return nil
}
m.mu.RLock()
if len(m.supplementalProviders) == 0 {
m.mu.RUnlock()
return nil
}
snapshot := make(map[unifiedresources.DataSource]MonitorSupplementalRecordsProvider, len(m.supplementalProviders))
for source, provider := range m.supplementalProviders {
if provider == nil {
continue
}
normalized := unifiedresources.DataSource(strings.ToLower(strings.TrimSpace(string(source))))
if normalized == "" {
continue
}
snapshot[normalized] = provider
}
m.mu.RUnlock()
if len(snapshot) == 0 {
return nil
}
return snapshot
}
func (m *Monitor) collectSupplementalRecordsBySource() map[unifiedresources.DataSource][]unifiedresources.IngestRecord {
providers := m.pollProviderSnapshotWithBuiltins()
manualProviders := m.supplementalProviderSnapshot()
if len(providers) == 0 && len(manualProviders) == 0 {
return nil
}
orgID := "default"
if m != nil {
if trimmed := strings.TrimSpace(m.orgID); trimmed != "" {
orgID = trimmed
}
}
recordsBySource := make(map[unifiedresources.DataSource][]unifiedresources.IngestRecord)
for _, provider := range providers {
supplementalProvider, ok := provider.(SupplementalRecordsPollProvider)
if !ok {
continue
}
source := supplementalProvider.SupplementalSource()
if strings.TrimSpace(string(source)) == "" {
continue
}
records := supplementalProvider.SupplementalRecords(m, orgID)
if len(records) == 0 {
continue
}
recordsBySource[source] = append(recordsBySource[source], records...)
}
if len(recordsBySource) == 0 {
recordsBySource = nil
}
if len(manualProviders) > 0 {
sources := make([]string, 0, len(manualProviders))
for source := range manualProviders {
sources = append(sources, string(source))
}
sort.Strings(sources)
if recordsBySource == nil {
recordsBySource = make(map[unifiedresources.DataSource][]unifiedresources.IngestRecord, len(sources))
}
for _, sourceName := range sources {
source := unifiedresources.DataSource(sourceName)
provider := manualProviders[source]
if provider == nil {
continue
}
records := provider.SupplementalRecords(m, orgID)
if len(records) == 0 {
continue
}
recordsBySource[source] = append(recordsBySource[source], records...)
}
}
if len(recordsBySource) == 0 {
return nil
}
return recordsBySource
}
func (m *Monitor) collectSupplementalChanges() []unifiedresources.ResourceChange {
providers := m.pollProviderSnapshotWithBuiltins()
manualProviders := m.supplementalProviderSnapshot()
if len(providers) == 0 && len(manualProviders) == 0 {
return nil
}
orgID := "default"
if m != nil {
if trimmed := strings.TrimSpace(m.orgID); trimmed != "" {
orgID = trimmed
}
}
changes := make([]unifiedresources.ResourceChange, 0)
for _, provider := range providers {
changeProvider, ok := provider.(SupplementalChangesPollProvider)
if !ok {
continue
}
changes = append(changes, changeProvider.SupplementalChanges(m, orgID)...)
}
if len(manualProviders) > 0 {
sources := make([]string, 0, len(manualProviders))
for source := range manualProviders {
sources = append(sources, string(source))
}
sort.Strings(sources)
for _, sourceName := range sources {
source := unifiedresources.DataSource(sourceName)
provider := manualProviders[source]
changeProvider, ok := provider.(MonitorSupplementalChangesProvider)
if !ok || changeProvider == nil {
continue
}
changes = append(changes, changeProvider.SupplementalChanges(m, orgID)...)
}
}
if len(changes) == 0 {
return nil
}
return changes
}