Files

97 lines
2.7 KiB
Go

package monitor
import "context"
import "fmt"
import "net"
import "strings"
import "time"
import "codit/internal/models"
// runCheck dispatches a monitor to its checker by family and returns the result.
// productID (the normalized server id) composes the probe's User-Agent / ICMP
// payload so no product name is hardcoded.
func runCheck(ctx context.Context, policy EgressPolicy, m models.Monitor, productID string, tlsCtx *TLSContext) models.MonitorCheckResult {
switch m.Type {
case models.MonitorTypeURL:
return checkURL(ctx, policy, m, productID, tlsCtx)
case models.MonitorTypeDNS:
return checkDNS(ctx, m)
case models.MonitorTypePing:
return checkPing(ctx, policy, m, productID)
default:
return models.MonitorCheckResult{OK: false, State: models.MonitorStateDown, Message: fmt.Sprintf("check type %q not implemented", m.Type)}
}
}
// hostAndPort splits a target into host and port, stripping any scheme/path and
// applying defaultPort when no port is present.
func hostAndPort(target string, defaultPort string) (string, string) {
var host string
var port string
var i int
var err error
target = strings.TrimSpace(target)
if i = strings.Index(target, "://"); i >= 0 {
target = target[i+3:]
}
if i = strings.IndexByte(target, '/'); i >= 0 {
target = target[:i]
}
host, port, err = net.SplitHostPort(target)
if err != nil {
return target, defaultPort
}
return host, port
}
// classifyDialError maps a connection error to a concise message, surfacing the
// egress-block and timeout cases.
func classifyDialError(err error) string {
var message string
message = err.Error()
if strings.Contains(message, ErrBlockedEgress.Error()) {
return "blocked by egress policy (target resolves to an internal address)"
}
if strings.Contains(message, context.DeadlineExceeded.Error()) || strings.Contains(message, "timeout") || strings.Contains(message, "Timeout") {
return "timed out"
}
return message
}
func checkTimeout(m models.Monitor) time.Duration {
var d time.Duration
d = time.Duration(m.TimeoutSec) * time.Second
if d <= 0 {
d = 10 * time.Second
}
return d
}
func failResult(message string) models.MonitorCheckResult {
return models.MonitorCheckResult{OK: false, State: models.MonitorStateDown, Message: message}
}
// worseState returns the more severe of two monitor states (down > degraded >
// up > pending), used to aggregate facet states into the monitor state.
func worseState(a string, b string) string {
if stateRank(b) > stateRank(a) {
return b
}
return a
}
func stateRank(s string) int {
switch s {
case models.MonitorStateDown:
return 3
case models.MonitorStateDegraded:
return 2
case models.MonitorStateUp:
return 1
default:
return 0
}
}