Score capacity pressure softly
This commit is contained in:
@@ -237,9 +237,14 @@ func scoreEndpointCandidateObservation(observation EndpointCandidateHealthObserv
|
|||||||
reasons = append(reasons, "history:failure")
|
reasons = append(reasons, "history:failure")
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(observation.LastFailureReason) != "" {
|
if strings.TrimSpace(observation.LastFailureReason) != "" {
|
||||||
|
if strings.TrimSpace(observation.LastFailureReason) == "capacity_limited" {
|
||||||
|
score -= 4
|
||||||
|
reasons = append(reasons, "capacity:limited")
|
||||||
|
} else {
|
||||||
score -= 8
|
score -= 8
|
||||||
reasons = append(reasons, "failure:recent")
|
reasons = append(reasons, "failure:recent")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return score, reasons
|
return score, reasons
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -353,6 +353,40 @@ func TestRankPeerEndpointCandidatesDoesNotRewardZeroLatencyFailure(t *testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRankPeerEndpointCandidatesTreatsCapacityAsSoftPressure(t *testing.T) {
|
||||||
|
now := time.Date(2026, 5, 16, 12, 0, 0, 0, time.UTC)
|
||||||
|
ranked := RankPeerEndpointCandidates([]PeerEndpointCandidate{
|
||||||
|
{
|
||||||
|
EndpointID: "node-b-quic",
|
||||||
|
NodeID: "node-b",
|
||||||
|
Transport: "direct_quic",
|
||||||
|
Address: "quic://node-b.example.test:19443",
|
||||||
|
Reachability: "public",
|
||||||
|
ConnectivityMode: "direct",
|
||||||
|
Priority: 10,
|
||||||
|
LastVerifiedAt: &now,
|
||||||
|
},
|
||||||
|
}, EndpointCandidateScoreOptions{
|
||||||
|
Now: now,
|
||||||
|
MaxVerificationAge: time.Minute,
|
||||||
|
Observations: map[string]EndpointCandidateHealthObservation{
|
||||||
|
"node-b-quic": {
|
||||||
|
EndpointID: "node-b-quic",
|
||||||
|
LastFailureReason: "capacity_limited",
|
||||||
|
ReliabilityScore: 95,
|
||||||
|
ObservedAt: now,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MaxObservationAge: time.Minute,
|
||||||
|
})
|
||||||
|
if len(ranked) != 1 || !containsReason(ranked[0].Reasons, "capacity:limited") {
|
||||||
|
t.Fatalf("capacity pressure reason missing: %+v", ranked)
|
||||||
|
}
|
||||||
|
if containsReason(ranked[0].Reasons, "failure:recent") {
|
||||||
|
t.Fatalf("capacity pressure treated as recent failure: %+v", ranked[0].Reasons)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func containsReason(reasons []string, reason string) bool {
|
func containsReason(reasons []string, reason string) bool {
|
||||||
for _, item := range reasons {
|
for _, item := range reasons {
|
||||||
if item == reason {
|
if item == reason {
|
||||||
|
|||||||
@@ -374,6 +374,9 @@ but saturated carrier.
|
|||||||
VPN fabric dial telemetry records the last capacity-limited endpoint and
|
VPN fabric dial telemetry records the last capacity-limited endpoint and
|
||||||
transport, making stream saturation visible without poisoning endpoint health
|
transport, making stream saturation visible without poisoning endpoint health
|
||||||
observations.
|
observations.
|
||||||
|
Endpoint ranking treats `capacity_limited` observations as a soft pressure
|
||||||
|
penalty instead of a hard recent failure, enabling load spreading without
|
||||||
|
marking the carrier unhealthy.
|
||||||
Cached QUIC carrier idle TTL is configurable through
|
Cached QUIC carrier idle TTL is configurable through
|
||||||
`RAP_VPN_FABRIC_QUIC_IDLE_TTL_SECONDS` / `-vpn-fabric-quic-idle-ttl` and
|
`RAP_VPN_FABRIC_QUIC_IDLE_TTL_SECONDS` / `-vpn-fabric-quic-idle-ttl` and
|
||||||
propagated by host-agent install profiles.
|
propagated by host-agent install profiles.
|
||||||
|
|||||||
Reference in New Issue
Block a user