128 lines
3.9 KiB
Go
128 lines
3.9 KiB
Go
package health
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
proxyDomain "proxy-pool/internal/domain/proxy"
|
|
)
|
|
|
|
// Candidate is one bounded due-index item. It identifies a check without
|
|
// carrying endpoint credentials or any persistence-specific representation.
|
|
type Candidate struct {
|
|
ProxyID string
|
|
State proxyDomain.State
|
|
Level Level
|
|
RoutingName string
|
|
TargetURL string
|
|
DueAt time.Time
|
|
}
|
|
|
|
// CandidateIdentity is stable across Controller instances and is suitable for
|
|
// deterministic task identity and jitter derivation.
|
|
func CandidateIdentity(candidate Candidate) string {
|
|
return candidate.ProxyID + "\x00" + string(candidate.Level) + "\x00" + candidate.RoutingName + "\x00" + candidate.TargetURL
|
|
}
|
|
|
|
type Priority uint8
|
|
|
|
const (
|
|
PriorityFetched Priority = iota + 1
|
|
PrioritySuspect
|
|
PriorityUnhealthy
|
|
PriorityAvailable
|
|
)
|
|
|
|
// PlannedTask is transport-neutral work ready for a shared leased task store.
|
|
type PlannedTask struct {
|
|
Candidate Candidate
|
|
Priority Priority
|
|
Deadline time.Time
|
|
NextDue time.Time
|
|
Attempts int
|
|
}
|
|
|
|
// TaskIDFor derives an idempotent task identity from all execution-relevant
|
|
// plan fields. Multiple Controller replicas can therefore offer the same plan
|
|
// without creating duplicate check work.
|
|
func TaskIDFor(task PlannedTask) string {
|
|
payload := CandidateIdentity(task.Candidate) + "\x00" + task.Deadline.UTC().Format(time.RFC3339Nano) + "\x00" +
|
|
task.NextDue.UTC().Format(time.RFC3339Nano) + "\x00" + strconv.Itoa(task.Attempts)
|
|
digest := sha256.Sum256([]byte(payload))
|
|
return "check_" + hex.EncodeToString(digest[:])
|
|
}
|
|
|
|
// TaskMaterial is short-lived proxy connection material. It crosses only the
|
|
// authenticated Checker control stream and must never be logged or persisted
|
|
// by a task queue.
|
|
type TaskMaterial struct {
|
|
Protocol proxyDomain.Scheme
|
|
Host string
|
|
Port uint16
|
|
SecretRef string
|
|
CredentialVersion string
|
|
Username string
|
|
Password string
|
|
}
|
|
|
|
func (TaskMaterial) Format(state fmt.State, _ rune) {
|
|
_, _ = state.Write([]byte("health.TaskMaterial{Credentials:<redacted>}"))
|
|
}
|
|
|
|
// TaskMaterialResolver resolves endpoint and credential material only when a
|
|
// task lease is granted. Checkers do not directly access Redis or PostgreSQL.
|
|
type TaskMaterialResolver interface {
|
|
ResolveCheckTask(context.Context, Candidate) (TaskMaterial, error)
|
|
}
|
|
|
|
type TaskMaterialResolverFunc func(context.Context, Candidate) (TaskMaterial, error)
|
|
|
|
func (resolver TaskMaterialResolverFunc) ResolveCheckTask(ctx context.Context, candidate Candidate) (TaskMaterial, error) {
|
|
return resolver(ctx, candidate)
|
|
}
|
|
|
|
// TaskClaim is one bounded pull request from a Checker process.
|
|
type TaskClaim struct {
|
|
CheckerID string
|
|
InstanceID string
|
|
MaxInFlight int
|
|
SupportedLevels []Level
|
|
}
|
|
|
|
// LeasedTask is a Controller-assigned task. Its material is kept in memory
|
|
// only for the task lease duration and is redacted from formatted values.
|
|
type LeasedTask struct {
|
|
TaskID string
|
|
LeaseToken string
|
|
ProxyID string
|
|
Protocol proxyDomain.Scheme
|
|
Host string
|
|
Port uint16
|
|
SecretRef string
|
|
CredentialVersion string
|
|
Username string
|
|
Password string
|
|
Level Level
|
|
RoutingName string
|
|
TargetURL string
|
|
Deadline time.Time
|
|
Attempts int
|
|
}
|
|
|
|
func (LeasedTask) Format(state fmt.State, _ rune) {
|
|
_, _ = state.Write([]byte("health.LeasedTask{Credentials:<redacted>}"))
|
|
}
|
|
|
|
// TaskBroker is the shared task lease boundary. Schedulers offer bounded
|
|
// work, Checkers claim work, and observation commits are fenced by a lease.
|
|
type TaskBroker interface {
|
|
Offer(context.Context, []PlannedTask) (int, error)
|
|
Claim(context.Context, TaskClaim) ([]LeasedTask, error)
|
|
AuthorizeObservation(context.Context, string, string, Observation, time.Time) error
|
|
CompleteObservation(context.Context, string, string, Observation, time.Time) error
|
|
}
|