remotes: add least-connections mirror strategy (round-robin remains default)
Add a per-remote mirror_strategy selector for the mirrorlist, supporting
round_robin (default, unchanged) and least_conn.
- models.Remote gains MirrorStrategy string + MirrorStrategy{RoundRobin,LeastConn}
constants and ValidateMirrorStrategy (enum check; least_conn requires a
non-empty mirrorlist). Empty behaves as round_robin for back-compat.
- DB: additive mirror_strategy TEXT NOT NULL DEFAULT 'round_robin' column
(CREATE TABLE + ADD COLUMN IF NOT EXISTS), wired through remoteCols/scanRemote/
CreateRemote/UpdateRemote; empty normalized to round_robin on write.
- Engine: least_conn starts each attempt with the pool URL holding the fewest
in-flight requests via a per-remote/per-URL atomic gauge (incremented around
each upstream call in head/fetch/checkUpstream), ties broken by the existing
round-robin rotation. Round-robin path and failover order unchanged;
single-URL pools are a no-op.
- Tests: unit tests for least-loaded selection, round-robin default, gauge
inc/dec, single-URL no-op, and strategy validation; DB round-trip covers the
new column; docker e2e adds a least_conn distribution test and a real dnf
install through a least_conn remote.
This commit is contained in:
@@ -74,6 +74,30 @@ func TestUpstreamPool(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMirrorStrategy(t *testing.T) {
|
||||
ml := []string{"https://m.example"}
|
||||
cases := []struct {
|
||||
name string
|
||||
remote Remote
|
||||
wantErr bool
|
||||
}{
|
||||
{"empty defaults ok", Remote{Mirrorlist: ml}, false},
|
||||
{"explicit round_robin ok", Remote{MirrorStrategy: MirrorStrategyRoundRobin, Mirrorlist: ml}, false},
|
||||
{"round_robin without mirrorlist ok", Remote{MirrorStrategy: MirrorStrategyRoundRobin}, false},
|
||||
{"least_conn with mirrorlist ok", Remote{MirrorStrategy: MirrorStrategyLeastConn, Mirrorlist: ml}, false},
|
||||
{"least_conn without mirrorlist rejected", Remote{MirrorStrategy: MirrorStrategyLeastConn}, true},
|
||||
{"unknown strategy rejected", Remote{MirrorStrategy: "random", Mirrorlist: ml}, true},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.remote.ValidateMirrorStrategy()
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Errorf("ValidateMirrorStrategy() err = %v, wantErr = %v", err, tc.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMirrorlist(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user