Files

76 lines
3.1 KiB
Go
Raw Permalink Normal View History

2026-08-26 06:41:07 +08:00
package externalprobe
import (
"context"
"errors"
"regexp"
"time"
"github.com/jackc/pgx/v5/pgxpool"
)
var (
serviceNamePattern = regexp.MustCompile(`^[a-z][a-z0-9-]{1,31}$`)
dependencyPattern = regexp.MustCompile(`^[a-z][a-z0-9_]{1,63}$`)
)
func PostgresObserver(pool *pgxpool.Pool, serviceName string) (Observer, error) {
if pool == nil || !serviceNamePattern.MatchString(serviceName) {
return nil, errors.New("external probe database and service name are required")
}
return func(ctx context.Context, observation Observation) error {
if !dependencyPattern.MatchString(observation.Dependency) {
return errors.New("external probe dependency name is invalid")
}
if observation.Interval <= 0 {
return errors.New("external probe interval is required")
}
latencyMS := observation.Latency.Milliseconds()
if latencyMS < 0 {
latencyMS = 0
}
intervalMS := observation.Interval.Milliseconds()
if intervalMS < 1 {
intervalMS = 1
}
checkedAt := observation.CheckedAt.UTC()
if checkedAt.IsZero() {
checkedAt = time.Now().UTC()
}
status := "AVAILABLE"
if !observation.Available {
status = "UNAVAILABLE"
if observation.ErrorCode == "" {
observation.ErrorCode = "PROBE_FAILED"
}
}
_, err := pool.Exec(ctx, `
INSERT INTO external_dependency_probe_state(service_name,dependency,status,checked_at,last_success_at,last_failure_at,last_error_code,latency_ms,probe_interval_ms,consecutive_failures)
VALUES($1,$2,$3::varchar,$4::timestamptz,CASE WHEN $3::varchar='AVAILABLE' THEN $4::timestamptz END,CASE WHEN $3::varchar='UNAVAILABLE' THEN $4::timestamptz END,NULLIF($5::varchar,''),$6,$7,CASE WHEN $3::varchar='UNAVAILABLE' THEN 1 ELSE 0 END)
ON CONFLICT(service_name,dependency) DO UPDATE SET
status=EXCLUDED.status,
checked_at=EXCLUDED.checked_at,
last_success_at=CASE WHEN EXCLUDED.status='AVAILABLE' THEN EXCLUDED.checked_at ELSE external_dependency_probe_state.last_success_at END,
last_failure_at=CASE WHEN EXCLUDED.status='UNAVAILABLE' THEN EXCLUDED.checked_at ELSE external_dependency_probe_state.last_failure_at END,
last_error_code=EXCLUDED.last_error_code,
latency_ms=EXCLUDED.latency_ms,
probe_interval_ms=EXCLUDED.probe_interval_ms,
consecutive_failures=CASE WHEN EXCLUDED.status='AVAILABLE' THEN 0 ELSE external_dependency_probe_state.consecutive_failures+1 END,
updated_at=now()`, serviceName, observation.Dependency, status, checkedAt, observation.ErrorCode, latencyMS, intervalMS)
return err
}, nil
}
func ReconcilePostgresState(ctx context.Context, pool *pgxpool.Pool, serviceName string, dependencies []string) error {
if pool == nil || !serviceNamePattern.MatchString(serviceName) {
return errors.New("external probe database and service name are required")
}
for _, dependency := range dependencies {
if !dependencyPattern.MatchString(dependency) {
return errors.New("external probe dependency name is invalid")
}
}
_, err := pool.Exec(ctx, `DELETE FROM external_dependency_probe_state WHERE service_name=$1 AND NOT (dependency=ANY(COALESCE($2::text[],ARRAY[]::text[])))`, serviceName, dependencies)
return err
}