Publish ITWorx Pulse source
Public source validation / validate (push) Failing after 3m8s

This commit is contained in:
ITWorx Pulse release export
2026-09-03 02:09:19 +02:00
commit bd774932d5
614 changed files with 77116 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
package network
import (
"context"
"errors"
"time"
"github.com/itworx/pulse/internal/host"
"github.com/itworx/pulse/internal/service"
)
type Aggregator struct {
Host host.Provider
Services service.Provider
Limits Limits
Now func() time.Time
}
func (a Aggregator) Snapshot(ctx context.Context) (Snapshot, error) {
if ctx == nil {
return Snapshot{}, errors.New("network context is nil")
}
if err := ctx.Err(); err != nil {
return Snapshot{}, err
}
now := time.Now().UTC()
if a.Now != nil {
now = a.Now().UTC()
}
hostSnapshot := host.UnknownSnapshot(now, "host", "agent", "source_unavailable")
if a.Host != nil {
next, err := a.Host.Snapshot(ctx)
if err != nil {
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return Snapshot{}, err
}
hostSnapshot = host.UnknownSnapshot(now, "host", "agent", "source_unavailable")
} else {
hostSnapshot = next
}
}
serviceSnapshot := service.UnknownSnapshot(now, "source_unavailable")
if a.Services != nil {
next, err := a.Services.Snapshot(ctx)
if err != nil {
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return Snapshot{}, err
}
serviceSnapshot = service.UnknownSnapshot(now, "source_unavailable")
} else {
serviceSnapshot = next
}
}
return BuildSnapshot(now, hostSnapshot, serviceSnapshot, nil, nil, a.Limits)
}
+35
View File
@@ -0,0 +1,35 @@
package network
import (
"context"
"errors"
"testing"
"time"
"github.com/itworx/pulse/internal/host"
"github.com/itworx/pulse/internal/service"
)
type errorHostProvider struct{}
func (errorHostProvider) Snapshot(context.Context) (host.Snapshot, error) {
return host.Snapshot{}, errors.New("host unavailable")
}
type staticServiceProvider struct{ snapshot service.Snapshot }
func (p staticServiceProvider) Snapshot(context.Context) (service.Snapshot, error) {
return p.snapshot, nil
}
func TestAggregatorKeepsPartialSourcesAvailable(t *testing.T) {
now := time.Date(2026, 8, 2, 12, 0, 0, 0, time.UTC)
provider := Aggregator{Host: errorHostProvider{}, Services: staticServiceProvider{snapshot: service.Snapshot{ObservedAt: now}}, Now: func() time.Time { return now }}
snapshot, err := provider.Snapshot(context.Background())
if err != nil {
t.Fatal(err)
}
if snapshot.Source.State != StateUnknown || snapshot.Health[0].State != StateUnknown || snapshot.Health[2].State != StateUnknown {
t.Fatalf("partial source did not remain unknown: %+v", snapshot)
}
}
+424
View File
@@ -0,0 +1,424 @@
package network
import (
"context"
"errors"
"sort"
"strings"
"time"
"github.com/itworx/pulse/internal/host"
"github.com/itworx/pulse/internal/service"
)
const ContractVersion = "v1"
const (
ScopeInternal = "internal"
ScopeGateway = "gateway"
ScopeDNS = "dns"
ScopeInternet = "internet"
StateUp = "up"
StateDegraded = "degraded"
StateDown = "down"
StateUnknown = "unknown"
)
type Interface struct {
Name string `json:"name"`
State string `json:"state"`
RxBytes uint64 `json:"rxBytes"`
TxBytes uint64 `json:"txBytes"`
RxErrors uint64 `json:"rxErrors"`
TxErrors uint64 `json:"txErrors"`
RxDrops uint64 `json:"rxDrops"`
TxDrops uint64 `json:"txDrops"`
}
type Health struct {
Scope string `json:"scope"`
State string `json:"state"`
CapabilityState string `json:"capabilityState"`
ConfigurationState string `json:"configurationState"`
Reason string `json:"reason,omitempty"`
SourceID string `json:"sourceId,omitempty"`
Freshness string `json:"freshness"`
ObservedAt time.Time `json:"observedAt"`
LatencyMS *int `json:"latencyMs,omitempty"`
}
type Certificate struct {
ID string `json:"id"`
ServiceID string `json:"serviceId"`
ObservedAt time.Time `json:"observedAt"`
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
Issuer string `json:"issuer,omitempty"`
Subject string `json:"subject,omitempty"`
HostnameValid *bool `json:"hostnameValid,omitempty"`
VerificationState string `json:"verificationState"`
}
type Event struct {
ID string `json:"id"`
Scope string `json:"scope"`
State string `json:"state"`
Reason string `json:"reason,omitempty"`
OccurredAt time.Time `json:"occurredAt"`
}
type Snapshot struct {
ContractVersion string `json:"contractVersion"`
ObservedAt time.Time `json:"observedAt"`
Source Source `json:"source"`
Health []Health `json:"health"`
Interfaces []Interface `json:"interfaces"`
Certificates []Certificate `json:"certificates"`
Events []Event `json:"events"`
}
type Source struct {
ID string `json:"id"`
Freshness string `json:"freshness"`
ObservedAt time.Time `json:"observedAt"`
State string `json:"state"`
Reason string `json:"reason,omitempty"`
}
type Signal struct {
Scope string
State string
Reason string
SourceID string
Freshness string
ObservedAt time.Time
LatencyMS *int
}
type Limits struct {
MaxInterfaces int
MaxCertificates int
MaxEvents int
}
func (l Limits) withDefaults() Limits {
if l.MaxInterfaces == 0 {
l.MaxInterfaces = 128
}
if l.MaxCertificates == 0 {
l.MaxCertificates = 100
}
if l.MaxEvents == 0 {
l.MaxEvents = 100
}
return l
}
func (l Limits) Validate() error {
if l.MaxInterfaces < 1 || l.MaxInterfaces > 256 || l.MaxCertificates < 1 || l.MaxCertificates > 500 || l.MaxEvents < 1 || l.MaxEvents > 500 {
return errors.New("network limits are outside safe bounds")
}
return nil
}
type Provider interface {
Snapshot(context.Context) (Snapshot, error)
}
func BuildSnapshot(now time.Time, hostSnapshot host.Snapshot, serviceSnapshot service.Snapshot, signals []Signal, events []Event, limits Limits) (Snapshot, error) {
limits = limits.withDefaults()
if err := limits.Validate(); err != nil {
return Snapshot{}, err
}
if now.IsZero() {
now = time.Now().UTC()
}
now = now.UTC()
interfaces := make([]Interface, 0, minInt(limits.MaxInterfaces, len(hostSnapshot.Network)))
hostFresh := hostSnapshot.Source.Freshness == host.Fresh && hostSnapshot.Source.State != host.StatusUnknown
for _, item := range hostSnapshot.Network {
if len(interfaces) >= limits.MaxInterfaces || strings.TrimSpace(item.Name) == "" {
break
}
state := normalizeState(item.State)
if !hostFresh {
state = StateUnknown
}
interfaces = append(interfaces, Interface{Name: item.Name, State: state, RxBytes: item.RxBytes, TxBytes: item.TxBytes, RxErrors: item.RxErrors, TxErrors: item.TxErrors, RxDrops: item.RxDrops, TxDrops: item.TxDrops})
}
sort.SliceStable(interfaces, func(i, j int) bool { return interfaces[i].Name < interfaces[j].Name })
health := []Health{
{Scope: ScopeInternal, State: internalState(hostSnapshot, hostFresh), CapabilityState: capabilityFromReason(hostSnapshot.Source.Reason), ConfigurationState: service.ConfigurationConfigured, Reason: internalReason(hostSnapshot, hostFresh), SourceID: hostSnapshot.Source.ID, Freshness: hostSnapshot.Source.Freshness, ObservedAt: timeValue(hostSnapshot.ObservedAt, now)},
{Scope: ScopeGateway, State: StateUnknown, CapabilityState: service.CapabilityAvailable, ConfigurationState: service.ConfigurationNotConfigured, Reason: "not_configured", Freshness: "unavailable", ObservedAt: now},
{Scope: ScopeDNS, State: dnsState(serviceSnapshot), CapabilityState: dnsCapability(serviceSnapshot), ConfigurationState: dnsConfiguration(serviceSnapshot), Reason: dnsReason(serviceSnapshot), Freshness: serviceFreshness(serviceSnapshot), ObservedAt: timeValue(serviceSnapshot.ObservedAt, now)},
{Scope: ScopeInternet, State: StateUnknown, CapabilityState: service.CapabilityAvailable, ConfigurationState: service.ConfigurationNotConfigured, Reason: "not_configured", Freshness: "unavailable", ObservedAt: now},
}
for _, signal := range signals {
if err := validateSignal(signal); err != nil {
return Snapshot{}, err
}
for index := range health {
if health[index].Scope == signal.Scope {
health[index] = Health{Scope: signal.Scope, State: normalizeState(signal.State), CapabilityState: service.CapabilityAvailable, ConfigurationState: service.ConfigurationConfigured, Reason: boundText(signal.Reason, 128), SourceID: boundText(signal.SourceID, 128), Freshness: boundText(signal.Freshness, 32), ObservedAt: timeValue(signal.ObservedAt, now), LatencyMS: boundedLatency(signal.LatencyMS)}
}
}
}
certificates := certificatesFromServices(serviceSnapshot, limits.MaxCertificates)
boundedEvents := append([]Event(nil), events...)
if len(boundedEvents) > limits.MaxEvents {
boundedEvents = boundedEvents[:limits.MaxEvents]
}
sort.SliceStable(boundedEvents, func(i, j int) bool {
if !boundedEvents[i].OccurredAt.Equal(boundedEvents[j].OccurredAt) {
return boundedEvents[i].OccurredAt.After(boundedEvents[j].OccurredAt)
}
return boundedEvents[i].ID < boundedEvents[j].ID
})
return Snapshot{ContractVersion: ContractVersion, ObservedAt: now, Source: Source{ID: "network-aggregate", Freshness: aggregateFreshness(hostSnapshot, serviceSnapshot), ObservedAt: now, State: aggregateState(health)}, Health: health, Interfaces: interfaces, Certificates: certificates, Events: boundedEvents}, nil
}
func validateSignal(signal Signal) error {
if signal.Scope != ScopeInternal && signal.Scope != ScopeGateway && signal.Scope != ScopeDNS && signal.Scope != ScopeInternet {
return errors.New("network signal scope is invalid")
}
if signal.State != StateUp && signal.State != StateDegraded && signal.State != StateDown && signal.State != StateUnknown {
return errors.New("network signal state is invalid")
}
if signal.LatencyMS != nil && *signal.LatencyMS < 0 {
return errors.New("network signal latency is invalid")
}
return nil
}
func internalState(snapshot host.Snapshot, fresh bool) string {
if !fresh {
return StateUnknown
}
switch snapshot.Status.State {
case host.StatusHealthy:
return StateUp
case host.StatusDegraded:
return StateDegraded
default:
return StateUnknown
}
}
func internalReason(snapshot host.Snapshot, fresh bool) string {
if !fresh {
if snapshot.Source.Reason != "" {
return snapshot.Source.Reason
}
return "source_stale"
}
if len(snapshot.Status.Reasons) > 0 {
return snapshot.Status.Reasons[0].Code
}
return "none"
}
func dnsState(snapshot service.Snapshot) string {
states := make([]string, 0)
for _, item := range snapshot.Services {
for _, config := range item.Probes {
if config.Type != "dns" {
continue
}
if item.State == service.StateUnknown {
states = append(states, StateUnknown)
continue
}
for _, result := range item.History {
if result.ProbeID == config.ID {
states = append(states, normalizeState(result.State))
break
}
}
}
}
if len(states) == 0 {
return StateUnknown
}
for _, state := range states {
if state == StateDown {
return StateDown
}
}
for _, state := range states {
if state == StateUnknown {
return StateUnknown
}
}
for _, state := range states {
if state == StateDegraded {
return StateDegraded
}
}
return StateUp
}
func dnsReason(snapshot service.Snapshot) string {
switch dnsState(snapshot) {
case StateDown:
return "dns_probe_failed"
case StateDegraded:
return "dns_probe_degraded"
case StateUp:
return "dns_probe_healthy"
default:
if dnsConfiguration(snapshot) == service.ConfigurationNotConfigured {
return "not_configured"
}
for _, item := range snapshot.Services {
for _, config := range item.Probes {
if config.Type == "dns" && item.Reason != "" {
return item.Reason
}
}
}
return "source_unavailable"
}
}
func dnsConfiguration(snapshot service.Snapshot) string {
for _, item := range snapshot.Services {
for _, config := range item.Probes {
if config.Type == "dns" {
return service.ConfigurationConfigured
}
}
}
if snapshot.CapabilityState == service.CapabilityUnavailable || snapshot.ConfigurationState == service.ConfigurationUnknown {
return service.ConfigurationUnknown
}
return service.ConfigurationNotConfigured
}
func dnsCapability(snapshot service.Snapshot) string {
if snapshot.CapabilityState == service.CapabilityUnsupported {
return service.CapabilityUnsupported
}
if snapshot.CapabilityState == service.CapabilityUnavailable {
return service.CapabilityUnavailable
}
return service.CapabilityAvailable
}
func capabilityFromReason(reason string) string {
if reason == "unsupported" {
return service.CapabilityUnsupported
}
if reason == "source_unavailable" {
return service.CapabilityUnavailable
}
return service.CapabilityAvailable
}
func serviceFreshness(snapshot service.Snapshot) string {
for _, item := range snapshot.Services {
if item.State != service.StateUnknown {
return "fresh"
}
}
return "unknown"
}
func certificatesFromServices(snapshot service.Snapshot, limit int) []Certificate {
byID := make(map[string]Certificate)
for _, item := range snapshot.Services {
if item.Certificate != nil && item.Certificate.ID != "" {
certificate := item.Certificate
byID[certificate.ID] = Certificate{ID: certificate.ID, ServiceID: certificate.ServiceID, ObservedAt: certificate.ObservedAt.UTC(), ExpiresAt: certificate.ExpiresAt, Issuer: certificate.Issuer, Subject: certificate.Subject, HostnameValid: certificate.HostnameValid, VerificationState: certificate.VerificationState}
}
for _, result := range item.History {
if result.Certificate == nil || result.Certificate.ID == "" {
continue
}
certificate := result.Certificate
byID[certificate.ID] = Certificate{ID: certificate.ID, ServiceID: certificate.ServiceID, ObservedAt: certificate.ObservedAt.UTC(), ExpiresAt: certificate.ExpiresAt, Issuer: certificate.Issuer, Subject: certificate.Subject, HostnameValid: certificate.HostnameValid, VerificationState: certificate.VerificationState}
}
}
items := make([]Certificate, 0, len(byID))
for _, item := range byID {
items = append(items, item)
}
sort.SliceStable(items, func(i, j int) bool {
if !items[i].ObservedAt.Equal(items[j].ObservedAt) {
return items[i].ObservedAt.After(items[j].ObservedAt)
}
return items[i].ID < items[j].ID
})
if len(items) > limit {
items = items[:limit]
}
return items
}
func aggregateFreshness(hostSnapshot host.Snapshot, serviceSnapshot service.Snapshot) string {
if hostSnapshot.Source.Freshness == host.Fresh || serviceFreshness(serviceSnapshot) == "fresh" {
return "fresh"
}
return "unknown"
}
func aggregateState(health []Health) string {
for _, item := range health {
if item.State == StateDown {
return StateDown
}
}
for _, item := range health {
if item.State == StateDegraded {
return StateDegraded
}
}
for _, item := range health {
if item.State == StateUnknown {
return StateUnknown
}
}
return StateUp
}
func normalizeState(value string) string {
switch value {
case StateUp, host.StatusHealthy:
return StateUp
case StateDegraded:
return StateDegraded
case StateDown:
return StateDown
default:
return StateUnknown
}
}
func boundedLatency(value *int) *int {
if value == nil || *value < 0 {
return nil
}
copy := *value
return &copy
}
func timeValue(value, fallback time.Time) time.Time {
if value.IsZero() {
return fallback
}
return value.UTC()
}
func boundText(value string, max int) string {
value = strings.TrimSpace(value)
if len(value) > max {
return value[:max]
}
return value
}
func minInt(left, right int) int {
if left < right {
return left
}
return right
}
+104
View File
@@ -0,0 +1,104 @@
package network
import (
"fmt"
"testing"
"time"
"github.com/itworx/pulse/internal/host"
"github.com/itworx/pulse/internal/probe"
"github.com/itworx/pulse/internal/service"
)
func TestBuildSnapshotSeparatesInternalInternetAndDNSHealth(t *testing.T) {
now := time.Date(2026, 8, 2, 12, 0, 0, 0, time.UTC)
hostSnapshot := host.Snapshot{Source: host.Source{ID: "agent-1", Freshness: host.Fresh, State: host.StatusHealthy, ObservedAt: now}, Status: host.Status{State: host.StatusHealthy}, ObservedAt: now, Network: []host.NetworkInterface{{Name: "eth0", State: "up", RxBytes: 100, TxBytes: 200, RxErrors: 2, TxErrors: 1, RxDrops: 3}}}
serviceSnapshot := service.Snapshot{ObservedAt: now, Services: []service.ServiceStatus{{ID: "dns-service", State: service.StateUp, Probes: []service.ProbeConfig{{ID: "dns-probe", Type: probe.TypeDNS}}, History: []probe.Result{{ProbeID: "dns-probe", State: service.StateDown, ObservedAt: now}}}}}
latency := 42
snapshot, err := BuildSnapshot(now, hostSnapshot, serviceSnapshot, []Signal{{Scope: ScopeInternet, State: StateDown, Reason: "internet_target_failed", SourceID: "probe-internet", Freshness: "fresh", ObservedAt: now, LatencyMS: &latency}}, nil, Limits{})
if err != nil {
t.Fatal(err)
}
states := make(map[string]Health, len(snapshot.Health))
for _, item := range snapshot.Health {
states[item.Scope] = item
}
if states[ScopeInternal].State != StateUp || states[ScopeDNS].State != StateDown || states[ScopeInternet].State != StateDown || states[ScopeGateway].State != StateUnknown {
t.Fatalf("health scopes conflated: %+v", states)
}
if len(snapshot.Interfaces) != 1 || snapshot.Interfaces[0].RxErrors != 2 || snapshot.Interfaces[0].RxDrops != 3 {
t.Fatalf("interface counters were not preserved: %+v", snapshot.Interfaces)
}
}
func TestBuildSnapshotMapsStaleHostToUnknownAndPreservesCertificate(t *testing.T) {
now := time.Date(2026, 8, 2, 12, 0, 0, 0, time.UTC)
expires := now.Add(30 * 24 * time.Hour)
hostSnapshot := host.Snapshot{Source: host.Source{ID: "agent-1", Freshness: host.Stale, State: host.StatusHealthy, Reason: "telemetry_stale", ObservedAt: now.Add(-time.Hour)}, Status: host.Status{State: host.StatusHealthy}, ObservedAt: now.Add(-time.Hour), Network: []host.NetworkInterface{{Name: "eth0", State: "up"}}}
valid := true
serviceSnapshot := service.Snapshot{ObservedAt: now, Services: []service.ServiceStatus{{ID: "tls-service", State: service.StateUp, Certificate: &probe.Certificate{ID: "cert-1", ServiceID: "tls-service", ObservedAt: now, ExpiresAt: &expires, HostnameValid: &valid, VerificationState: "valid"}}}}
snapshot, err := BuildSnapshot(now, hostSnapshot, serviceSnapshot, nil, nil, Limits{})
if err != nil {
t.Fatal(err)
}
if snapshot.Health[0].State != StateUnknown || snapshot.Interfaces[0].State != StateUnknown || snapshot.Health[0].Reason != "telemetry_stale" {
t.Fatalf("stale host was not unknown: %+v", snapshot)
}
if len(snapshot.Certificates) != 1 || snapshot.Certificates[0].ID != "cert-1" {
t.Fatalf("certificate inventory missing: %+v", snapshot.Certificates)
}
}
func TestBuildSnapshotRejectsInvalidSignalsAndBounds(t *testing.T) {
if _, err := BuildSnapshot(time.Now(), host.Snapshot{}, service.Snapshot{}, []Signal{{Scope: "unknown", State: StateUp}}, nil, Limits{}); err == nil {
t.Fatal("expected invalid scope")
}
if _, err := BuildSnapshot(time.Now(), host.Snapshot{}, service.Snapshot{}, nil, nil, Limits{MaxInterfaces: 0, MaxCertificates: 0, MaxEvents: 501}); err == nil {
t.Fatal("expected invalid limit")
}
}
func TestBuildSnapshotDistinguishesDNSConfigurationStalenessAndAvailability(t *testing.T) {
now := time.Date(2026, 8, 12, 1, 0, 0, 0, time.UTC)
hostSnapshot := host.Snapshot{Source: host.Source{Freshness: host.Fresh, State: host.StatusHealthy}, Status: host.Status{State: host.StatusHealthy}, ObservedAt: now}
cases := []struct {
name, reason, capability, configuration string
snapshot service.Snapshot
}{
{name: "not configured", snapshot: service.Snapshot{CapabilityState: service.CapabilityAvailable, ConfigurationState: service.ConfigurationNotConfigured}, reason: "not_configured", capability: service.CapabilityAvailable, configuration: service.ConfigurationNotConfigured},
{name: "stale", snapshot: service.Snapshot{CapabilityState: service.CapabilityAvailable, ConfigurationState: service.ConfigurationConfigured, Services: []service.ServiceStatus{{ID: "dns", State: service.StateUnknown, Reason: "stale_probe", Probes: []service.ProbeConfig{{ID: "dns-probe", Type: probe.TypeDNS, Enabled: true}}}}}, reason: "stale_probe", capability: service.CapabilityAvailable, configuration: service.ConfigurationConfigured},
{name: "unavailable", snapshot: service.UnknownSnapshot(now, "source_unavailable"), reason: "source_unavailable", capability: service.CapabilityUnavailable, configuration: service.ConfigurationUnknown},
}
for _, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) {
snapshot, err := BuildSnapshot(now, hostSnapshot, testCase.snapshot, nil, nil, Limits{})
if err != nil {
t.Fatal(err)
}
dns := snapshot.Health[2]
if dns.Reason != testCase.reason || dns.CapabilityState != testCase.capability || dns.ConfigurationState != testCase.configuration {
t.Fatalf("DNS state was conflated: %+v", dns)
}
})
}
}
func TestBuildSnapshotTargetScaleIsBounded(t *testing.T) {
now := time.Date(2026, 8, 2, 12, 0, 0, 0, time.UTC)
hostSnapshot := host.Snapshot{Source: host.Source{Freshness: host.Fresh, State: host.StatusHealthy}, Status: host.Status{State: host.StatusHealthy}, ObservedAt: now}
for index := 0; index < 300; index++ {
hostSnapshot.Network = append(hostSnapshot.Network, host.NetworkInterface{Name: fmt.Sprintf("eth-%03d", index), State: "up"})
}
serviceSnapshot := service.Snapshot{ObservedAt: now}
valid := true
for index := 0; index < 150; index++ {
serviceSnapshot.Services = append(serviceSnapshot.Services, service.ServiceStatus{ID: fmt.Sprintf("service-%03d", index), State: service.StateUp, History: []probe.Result{{ProbeID: fmt.Sprintf("probe-%03d", index), ObservedAt: now, Certificate: &probe.Certificate{ID: fmt.Sprintf("cert-%03d", index), ServiceID: fmt.Sprintf("service-%03d", index), ObservedAt: now, HostnameValid: &valid, VerificationState: "valid"}}}})
}
snapshot, err := BuildSnapshot(now, hostSnapshot, serviceSnapshot, nil, nil, Limits{MaxInterfaces: 128, MaxCertificates: 100, MaxEvents: 100})
if err != nil {
t.Fatal(err)
}
if len(snapshot.Interfaces) != 128 || len(snapshot.Certificates) != 100 {
t.Fatalf("network target bounds failed: interfaces=%d certificates=%d", len(snapshot.Interfaces), len(snapshot.Certificates))
}
}