Files
zeekayandHanzo Dev 78ab4f2f83 Declare operations on the group that serves them, and build on Go 1.27
zip's registration is a verb method on the group, so a route that is not a typed
operation says which method it answers with. The security package's own
two-method router goes with it: it existed so both the app and a sub-router could
satisfy one name, and restating a routing surface is what let the two disagree —
the methods it named no longer exist. The app spells itself as a group, which
registers at exactly the paths it did.

The toolchain and the pin move together, because below zip v1.36.50 the JSON
encoder was selected on a build tag: a repo whose go directive rises first writes
a wire that is neither the old release's nor the new one's until the pin catches
up.

metrics_test.go still does not compile, before and after: it compares a metric
registry against a gatherer from a different metrics library, so the two Gather
signatures disagree. That is its own change.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-17 09:58:39 -07:00

85 lines
3.0 KiB
Go

package watchdog
import (
"context"
"time"
"github.com/TwiN/logr"
"hanzo.ai/status/config"
"hanzo.ai/status/config/suite"
"hanzo.ai/status/metrics"
"hanzo.ai/status/storage/store"
)
// monitorSuite monitors a suite by executing it at regular intervals
func monitorSuite(s *suite.Suite, cfg *config.Config, extraLabels []string, ctx context.Context) {
// Execute immediately on start
executeSuite(s, cfg, extraLabels)
// Set up ticker for periodic execution
ticker := time.NewTicker(s.Interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
logr.Warnf("[watchdog.monitorSuite] Canceling monitoring for suite=%s", s.Name)
return
case <-ticker.C:
executeSuite(s, cfg, extraLabels)
}
}
}
// executeSuite executes a suite with proper concurrency control
func executeSuite(s *suite.Suite, cfg *config.Config, extraLabels []string) {
// Acquire semaphore to limit concurrent suite monitoring
if err := monitoringSemaphore.Acquire(ctx, 1); err != nil {
// Only fails if context is cancelled (during shutdown)
logr.Debugf("[watchdog.executeSuite] Context cancelled, skipping execution: %s", err.Error())
return
}
defer monitoringSemaphore.Release(1)
// Check connectivity if configured
if cfg.Connectivity != nil && cfg.Connectivity.Checker != nil && !cfg.Connectivity.Checker.IsConnected() {
logr.Infof("[watchdog.executeSuite] No connectivity; skipping suite=%s", s.Name)
return
}
logr.Debugf("[watchdog.executeSuite] Monitoring group=%s; suite=%s; key=%s", s.Group, s.Name, s.Key())
// Execute the suite using its Execute method
result := s.Execute()
// Publish metrics for the suite execution
if cfg.Metrics {
metrics.PublishMetricsForSuite(s, result, extraLabels)
}
// Store result
UpdateSuiteStatus(s, result)
// Handle alerting for suite endpoints
for i, ep := range s.Endpoints {
if i < len(result.EndpointResults) {
epResult := result.EndpointResults[i]
// Handle alerting if configured and not under maintenance
if cfg.Alerting != nil && !cfg.Maintenance.IsUnderMaintenance() {
// Check if endpoint is under maintenance
inEndpointMaintenanceWindow := false
for _, maintenanceWindow := range ep.MaintenanceWindows {
if maintenanceWindow.IsUnderMaintenance() {
logr.Debug("[watchdog.executeSuite] Endpoint under maintenance window")
inEndpointMaintenanceWindow = true
break
}
}
if !inEndpointMaintenanceWindow {
HandleAlerting(ep, epResult, cfg.Alerting)
}
}
}
}
logr.Infof("[watchdog.executeSuite] Completed suite=%s; success=%v; errors=%d; duration=%v; endpoints_executed=%d/%d", s.Name, result.Success, len(result.Errors), result.Duration, len(result.EndpointResults), len(s.Endpoints))
}
// UpdateSuiteStatus persists the suite result in the database
func UpdateSuiteStatus(s *suite.Suite, result *suite.Result) {
if err := store.Get().InsertSuiteResult(s, result); err != nil {
logr.Errorf("[watchdog.executeSuite] Failed to insert suite result for suite=%s: %v", s.Name, err)
}
}