Use live QUIC pressure for endpoint ranking
This commit is contained in:
@@ -5333,6 +5333,10 @@ func vpnFabricSessionTargets(meshState *syntheticMeshState, nextHop string) []me
|
||||
if meshState.VPNFabricSessionDialStats != nil {
|
||||
capacityPressure = meshState.VPNFabricSessionDialStats.capacityPressureForScoring(2 * time.Minute)
|
||||
}
|
||||
capacityPressure = mergeEndpointCapacityPressure(
|
||||
capacityPressure,
|
||||
quicEndpointCapacityPressureForScoring(candidates, meshState.VPNFabricQUICTransport, time.Now().UTC()),
|
||||
)
|
||||
ranked := mesh.RankPeerEndpointCandidates(candidates, mesh.EndpointCandidateScoreOptions{
|
||||
ChannelClass: mesh.SyntheticChannelFabricControl,
|
||||
Now: time.Now().UTC(),
|
||||
@@ -5370,6 +5374,86 @@ func vpnFabricSessionTargets(meshState *syntheticMeshState, nextHop string) []me
|
||||
return out
|
||||
}
|
||||
|
||||
func quicEndpointCapacityPressureForScoring(candidates []mesh.PeerEndpointCandidate, transport *mesh.QUICFabricTransport, now time.Time) map[string]mesh.EndpointCandidateCapacityPressure {
|
||||
if len(candidates) == 0 || transport == nil {
|
||||
return nil
|
||||
}
|
||||
return quicEndpointCapacityPressureForScoringFromSnapshot(candidates, transport.Snapshot(), now)
|
||||
}
|
||||
|
||||
func quicEndpointCapacityPressureForScoringFromSnapshot(candidates []mesh.PeerEndpointCandidate, snapshot mesh.QUICFabricTransportSnapshot, now time.Time) map[string]mesh.EndpointCandidateCapacityPressure {
|
||||
if len(candidates) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(snapshot.Connections) == 0 {
|
||||
return nil
|
||||
}
|
||||
pressureByEndpoint := map[string]mesh.QUICFabricConnSnapshot{}
|
||||
for _, conn := range snapshot.Connections {
|
||||
endpoint := normalizeQUICCapacityEndpoint(conn.Endpoint)
|
||||
if endpoint == "" || conn.CapacityPressurePercent <= 0 {
|
||||
continue
|
||||
}
|
||||
existing := pressureByEndpoint[endpoint]
|
||||
if conn.CapacityPressurePercent > existing.CapacityPressurePercent {
|
||||
pressureByEndpoint[endpoint] = conn
|
||||
}
|
||||
}
|
||||
if len(pressureByEndpoint) == 0 {
|
||||
return nil
|
||||
}
|
||||
if now.IsZero() {
|
||||
now = time.Now().UTC()
|
||||
}
|
||||
out := map[string]mesh.EndpointCandidateCapacityPressure{}
|
||||
for _, candidate := range candidates {
|
||||
endpointID := strings.TrimSpace(candidate.EndpointID)
|
||||
if endpointID == "" {
|
||||
continue
|
||||
}
|
||||
conn, ok := pressureByEndpoint[normalizeQUICCapacityEndpoint(candidate.Address)]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
count := int64((conn.CapacityPressurePercent + 9) / 10)
|
||||
if count <= 0 {
|
||||
count = 1
|
||||
}
|
||||
out[endpointID] = mesh.EndpointCandidateCapacityPressure{
|
||||
EndpointID: endpointID,
|
||||
Count: count,
|
||||
LastSeenUnixSec: now.Unix(),
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func mergeEndpointCapacityPressure(primary, secondary map[string]mesh.EndpointCandidateCapacityPressure) map[string]mesh.EndpointCandidateCapacityPressure {
|
||||
if len(primary) == 0 {
|
||||
return secondary
|
||||
}
|
||||
if len(secondary) == 0 {
|
||||
return primary
|
||||
}
|
||||
out := make(map[string]mesh.EndpointCandidateCapacityPressure, len(primary)+len(secondary))
|
||||
for endpointID, pressure := range primary {
|
||||
out[endpointID] = pressure
|
||||
}
|
||||
for endpointID, pressure := range secondary {
|
||||
existing, ok := out[endpointID]
|
||||
if !ok || pressure.Count > existing.Count || pressure.LastSeenUnixSec > existing.LastSeenUnixSec {
|
||||
out[endpointID] = pressure
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func normalizeQUICCapacityEndpoint(endpoint string) string {
|
||||
endpoint = strings.TrimRight(strings.TrimSpace(endpoint), "/")
|
||||
endpoint = strings.TrimPrefix(endpoint, "quic://")
|
||||
return strings.ToLower(endpoint)
|
||||
}
|
||||
|
||||
func mergedEndpointCandidateObservations(remote map[string]mesh.EndpointCandidateHealthObservation, local map[string]mesh.EndpointCandidateHealthObservation) map[string]mesh.EndpointCandidateHealthObservation {
|
||||
if len(remote) == 0 && len(local) == 0 {
|
||||
return nil
|
||||
|
||||
@@ -1132,6 +1132,41 @@ func TestVPNFabricSessionTargetsUseCapacityPressureForLoadSpread(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestQUICEndpointCapacityPressureForScoringUsesLiveSnapshot(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
pressure := quicEndpointCapacityPressureForScoringFromSnapshot([]mesh.PeerEndpointCandidate{
|
||||
{
|
||||
EndpointID: "node-b-quic-a",
|
||||
Transport: "direct_quic",
|
||||
Address: "quic://NODE-B-A.example.test:19443/",
|
||||
},
|
||||
{
|
||||
EndpointID: "node-b-quic-b",
|
||||
Transport: "direct_quic",
|
||||
Address: "quic://node-b-b.example.test:19443",
|
||||
},
|
||||
}, mesh.QUICFabricTransportSnapshot{
|
||||
Connections: []mesh.QUICFabricConnSnapshot{
|
||||
{
|
||||
Endpoint: "node-b-a.example.test:19443",
|
||||
ActiveStreams: 5,
|
||||
MaxStreams: 10,
|
||||
CapacityPressurePercent: 50,
|
||||
},
|
||||
},
|
||||
}, now)
|
||||
if len(pressure) != 1 {
|
||||
t.Fatalf("pressure count = %d, want 1: %+v", len(pressure), pressure)
|
||||
}
|
||||
got := pressure["node-b-quic-a"]
|
||||
if got.EndpointID != "node-b-quic-a" || got.Count != 5 || got.LastSeenUnixSec != now.Unix() {
|
||||
t.Fatalf("unexpected pressure: %+v", got)
|
||||
}
|
||||
if _, ok := pressure["node-b-quic-b"]; ok {
|
||||
t.Fatalf("unpressured endpoint was included: %+v", pressure)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergedEndpointCandidateObservationsKeepsNewest(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
merged := mergedEndpointCandidateObservations(
|
||||
|
||||
Reference in New Issue
Block a user