Skip to content

Commit 873e149

Browse files
Merge pull request #3171 from jhiemstrawisc/quick-stat-fix
Be consistent about choice of TTLCache keys in Director stat utilities
2 parents 894da13 + 2b2a128 commit 873e149

File tree

3 files changed

+135
-4
lines changed

3 files changed

+135
-4
lines changed

director/sort_algorithms.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ func availabilityWeightFn(ad server_structs.ServerAd, availMap map[string]bool,
289289
return 1.0, true
290290
}
291291

292-
avail, ok := availMap[ad.Name]
292+
avail, ok := availMap[ad.URL.String()]
293293
if !ok {
294294
return 0, false
295295
}

director/sort_algorithms_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ func TestStatusWeightFn(t *testing.T) {
636636
func TestAvailabilityWeightFn(t *testing.T) {
637637
t.Cleanup(test_utils.SetupTestLogging(t))
638638

639-
sAd := server_structs.ServerAd{}
639+
sAd := server_structs.ServerAd{URL: url.URL{Scheme: "https", Host: "ad1:8443"}}
640640
sAd.Initialize("ad1")
641641

642642
testCases := []struct {
@@ -648,14 +648,14 @@ func TestAvailabilityWeightFn(t *testing.T) {
648648
}{
649649
{
650650
name: "object available at server",
651-
availMap: map[string]bool{sAd.Name: true},
651+
availMap: map[string]bool{sAd.URL.String(): true},
652652
sAd: sAd,
653653
weightValid: true,
654654
expectedWeight: 2.0,
655655
},
656656
{
657657
name: "object not available at server",
658-
availMap: map[string]bool{sAd.Name: false},
658+
availMap: map[string]bool{sAd.URL.String(): false},
659659
sAd: sAd,
660660
weightValid: true,
661661
expectedWeight: 0.5,

director/stat_test.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import (
3030
"testing"
3131
"time"
3232

33+
"github.com/gin-gonic/gin"
34+
"github.com/google/uuid"
3335
"github.com/jellydator/ttlcache/v3"
3436
log "github.com/sirupsen/logrus"
3537
"github.com/stretchr/testify/assert"
@@ -1038,3 +1040,132 @@ func TestSendHeadReq(t *testing.T) {
10381040
assert.Nil(t, meta)
10391041
})
10401042
}
1043+
1044+
// TestGenerateAvailabilityMaps verifies that the maps returned by generateAvailabilityMaps
1045+
// are keyed by ad.URL.String() rather than ad.Name. This is the key that availabilityWeightFn
1046+
// uses for lookup, so a mismatch would silently disable the availability axis of adaptive sort.
1047+
func TestGenerateAvailabilityMaps(t *testing.T) {
1048+
setGinTestMode()
1049+
t.Cleanup(test_utils.SetupTestLogging(t))
1050+
1051+
// Save and restore global serverAds so we don't interfere with other tests.
1052+
oldAds := serverAds
1053+
t.Cleanup(func() {
1054+
cleanupMock()
1055+
serverAds = oldAds
1056+
})
1057+
serverAds = ttlcache.New(ttlcache.WithTTL[string, *server_structs.Advertisement](15 * time.Minute))
1058+
1059+
makeCtx := func(method, reqPath string) *gin.Context {
1060+
w := httptest.NewRecorder()
1061+
c, _ := gin.CreateTestContext(w)
1062+
c.Request = httptest.NewRequest(method, reqPath, nil)
1063+
return c
1064+
}
1065+
1066+
originAd := server_structs.ServerAd{
1067+
URL: url.URL{Scheme: "https", Host: "origin.example.com:8443"},
1068+
AuthURL: url.URL{Scheme: "https", Host: "origin-auth.example.com:8444"},
1069+
Caps: server_structs.Capabilities{PublicReads: true},
1070+
Type: server_structs.OriginType.String(),
1071+
}
1072+
originAd.Initialize("my-origin")
1073+
1074+
cacheAd := server_structs.ServerAd{
1075+
URL: url.URL{Scheme: "https", Host: "cache.example.com:8443"},
1076+
AuthURL: url.URL{Scheme: "https", Host: "cache-auth.example.com:8444"},
1077+
Caps: server_structs.Capabilities{PublicReads: true},
1078+
Type: server_structs.CacheType.String(),
1079+
}
1080+
cacheAd.Initialize("my-cache")
1081+
1082+
bestNSAd := server_structs.NamespaceAdV2{
1083+
Path: "/foo",
1084+
Caps: server_structs.Capabilities{PublicReads: true},
1085+
}
1086+
reqID := uuid.New()
1087+
1088+
// When stat is skipped (neither CheckCachePresence nor CheckOriginPresence enabled),
1089+
// generateAvailabilityMaps assumes all servers are available and must key those maps
1090+
// by ad.URL.String() — not by ad.Name. Before the fix, the Name key meant
1091+
// availabilityWeightFn could never find a match, causing adaptive sort to treat all
1092+
// servers as equally available regardless of stat results.
1093+
t.Run("skip-stat-path-maps-keyed-by-url", func(t *testing.T) {
1094+
server_utils.ResetTestState()
1095+
require.NoError(t, param.Set(param.Director_CheckCachePresence.GetName(), false))
1096+
require.NoError(t, param.Set(param.Director_CheckOriginPresence.GetName(), false))
1097+
1098+
// Use an origin-redirect path so both skipped-stat branches execute.
1099+
ctx := makeCtx(http.MethodGet, "/api/v1.0/director/origin/foo/test.txt")
1100+
oMap, cMap, err := generateAvailabilityMaps(
1101+
ctx,
1102+
[]server_structs.ServerAd{originAd},
1103+
[]server_structs.ServerAd{cacheAd},
1104+
bestNSAd, reqID,
1105+
)
1106+
require.NoError(t, err)
1107+
1108+
// Keys MUST be ad.URL.String() — availabilityWeightFn does availMap[ad.URL.String()].
1109+
assert.True(t, oMap[originAd.URL.String()],
1110+
"origin availability map must be keyed by ad.URL.String() %q", originAd.URL.String())
1111+
assert.False(t, oMap[originAd.Name],
1112+
"origin availability map must NOT be keyed by ad.Name %q", originAd.Name)
1113+
1114+
assert.True(t, cMap[cacheAd.URL.String()],
1115+
"cache availability map must be keyed by ad.URL.String() %q", cacheAd.URL.String())
1116+
assert.False(t, cMap[cacheAd.Name],
1117+
"cache availability map must NOT be keyed by ad.Name %q", cacheAd.Name)
1118+
})
1119+
1120+
// When stat is enabled and a server responds positively, the resulting map entry
1121+
// must still use ad.URL.String() as the key. Before the fix, a successful stat wrote
1122+
// ad.Name — again invisible to availabilityWeightFn.
1123+
t.Run("stat-results-maps-keyed-by-url", func(t *testing.T) {
1124+
server_utils.ResetTestState()
1125+
require.NoError(t, param.Set(param.Director_CheckCachePresence.GetName(), true))
1126+
require.NoError(t, param.Set(param.Director_CheckOriginPresence.GetName(), false))
1127+
require.NoError(t, param.Set(param.Director_StatTimeout.GetName(), 2*time.Second))
1128+
require.NoError(t, param.Set(param.Director_MinStatResponse.GetName(), 1))
1129+
require.NoError(t, param.Set(param.Director_MaxStatResponse.GetName(), 1))
1130+
1131+
// Mock HTTP server that returns 200 with Content-Length for every HEAD request.
1132+
mockSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1133+
w.Header().Set("Content-Length", "10")
1134+
w.WriteHeader(http.StatusOK)
1135+
}))
1136+
t.Cleanup(mockSrv.Close)
1137+
1138+
srvURL, err := url.Parse(mockSrv.URL)
1139+
require.NoError(t, err)
1140+
1141+
statCacheAd := server_structs.ServerAd{
1142+
URL: *srvURL,
1143+
Caps: server_structs.Capabilities{PublicReads: true},
1144+
Type: server_structs.CacheType.String(),
1145+
}
1146+
statCacheAd.Initialize("stat-cache")
1147+
1148+
// Populate serverAds and statUtils so queryServersForObject can service the request.
1149+
serverAds.Set(statCacheAd.URL.String(),
1150+
&server_structs.Advertisement{ServerAd: statCacheAd},
1151+
ttlcache.DefaultTTL)
1152+
initMockStatUtils()
1153+
t.Cleanup(cleanupMock)
1154+
1155+
// /api/v1.0/director/object paths are cache requests: shouldStatCaches → true,
1156+
// shouldStatOrigins → false (isCacheRequest && len(cAds) > 0).
1157+
ctx := makeCtx(http.MethodGet, "/api/v1.0/director/object/foo/test.txt")
1158+
_, cMap, err := generateAvailabilityMaps(
1159+
ctx,
1160+
[]server_structs.ServerAd{},
1161+
[]server_structs.ServerAd{statCacheAd},
1162+
bestNSAd, reqID,
1163+
)
1164+
require.NoError(t, err)
1165+
1166+
assert.True(t, cMap[statCacheAd.URL.String()],
1167+
"cache map must be keyed by ad.URL.String() %q after a successful stat", statCacheAd.URL.String())
1168+
assert.False(t, cMap[statCacheAd.Name],
1169+
"cache map must NOT be keyed by ad.Name %q after stat — this broke adaptive sort", statCacheAd.Name)
1170+
})
1171+
}

0 commit comments

Comments
 (0)