Skip to content

Commit efebb68

Browse files
authored
Merge pull request #1935 from jhiemstrawisc/lotman-plumbing
Plumb Lotman config through to XRootD purge plugin
2 parents c98805b + 946dc95 commit efebb68

File tree

12 files changed

+435
-178
lines changed

12 files changed

+435
-178
lines changed

config/config.go

Lines changed: 57 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import (
3333
"os"
3434
"path/filepath"
3535
"reflect"
36-
"slices"
3736
"sort"
3837
"strconv"
3938
"strings"
@@ -55,6 +54,7 @@ import (
5554
"github.com/pelicanplatform/pelican/param"
5655
"github.com/pelicanplatform/pelican/pelican_url"
5756
"github.com/pelicanplatform/pelican/server_structs"
57+
"github.com/pelicanplatform/pelican/utils"
5858
)
5959

6060
// Structs holding the OAuth2 state (and any other OSDF config needed)
@@ -165,8 +165,7 @@ var (
165165

166166
RestartFlag = make(chan any) // A channel flag to restart the server instance that launcher listens to (including cache)
167167

168-
watermarkUnits = []byte{'k', 'm', 'g', 't'}
169-
validPrefixes = map[ConfigPrefix]bool{
168+
validPrefixes = map[ConfigPrefix]bool{
170169
PelicanPrefix: true,
171170
OsdfPrefix: true,
172171
StashPrefix: true,
@@ -627,45 +626,6 @@ func handleDeprecatedConfig() {
627626
}
628627
}
629628

630-
func checkWatermark(wmStr string) (bool, int64, error) {
631-
wmNum, err := strconv.Atoi(wmStr)
632-
if err == nil {
633-
if wmNum > 100 || wmNum < 0 {
634-
return false, 0, errors.Errorf("watermark value %s must be a integer number in range [0, 100]. Refer to parameter page for details: https://docs.pelicanplatform.org/parameters#Cache-HighWatermark", wmStr)
635-
}
636-
return true, int64(wmNum), nil
637-
// Not an integer number, check if it's in form of <int>k|m|g|t
638-
} else {
639-
if len(wmStr) < 1 {
640-
return false, 0, errors.Errorf("watermark value %s is empty.", wmStr)
641-
}
642-
unit := wmStr[len(wmStr)-1]
643-
if slices.Contains(watermarkUnits, unit) {
644-
byteNum, err := strconv.Atoi(wmStr[:len(wmStr)-1])
645-
// Bytes portion is not an integer
646-
if err != nil {
647-
return false, 0, errors.Errorf("watermark value %s is neither a percentage integer (e.g. 95) or a valid bytes. Refer to parameter page for details: https://docs.pelicanplatform.org/parameters#Cache-HighWatermark", wmStr)
648-
} else {
649-
switch unit {
650-
case 'k':
651-
return true, int64(byteNum) * 1024, nil
652-
case 'm':
653-
return true, int64(byteNum) * 1024 * 1024, nil
654-
case 'g':
655-
return true, int64(byteNum) * 1024 * 1024 * 1024, nil
656-
case 't':
657-
return true, int64(byteNum) * 1024 * 1024 * 1024 * 1024, nil
658-
default:
659-
return false, 0, errors.Errorf("watermark value %s is neither a percentage integer (e.g. 95) or a valid byte. Bytes representation is missing unit (k|m|g|t). Refer to parameter page for details: https://docs.pelicanplatform.org/parameters#Cache-HighWatermark", wmStr)
660-
}
661-
}
662-
} else {
663-
// Doesn't contain k|m|g|t suffix
664-
return false, 0, errors.Errorf("watermark value %s is neither a percentage integer (e.g. 95) or a valid byte. Bytes representation is missing unit (k|m|g|t). Refer to parameter page for details: https://docs.pelicanplatform.org/parameters#Cache-HighWatermark", wmStr)
665-
}
666-
}
667-
}
668-
669629
func setupTranslation() error {
670630
err := en_translations.RegisterDefaultTranslations(validate, GetEnTranslator())
671631
if err != nil {
@@ -1331,18 +1291,63 @@ func InitServer(ctx context.Context, currentServers server_structs.ServerType) e
13311291
}
13321292

13331293
if param.Cache_LowWatermark.IsSet() || param.Cache_HighWaterMark.IsSet() {
1334-
lowWmStr := param.Cache_LowWatermark.GetString()
1335-
highWmStr := param.Cache_HighWaterMark.GetString()
1336-
ok, highWmNum, err := checkWatermark(highWmStr)
1337-
if !ok && err != nil {
1338-
return errors.Wrap(err, "invalid Cache.HighWaterMark value")
1294+
lowWm, lwmIsAbs, err := utils.ValidateWatermark(param.Cache_LowWatermark.GetName(), false)
1295+
if err != nil {
1296+
return err
13391297
}
1340-
ok, lowWmNum, err := checkWatermark(lowWmStr)
1341-
if !ok && err != nil {
1342-
return errors.Wrap(err, "invalid Cache.LowWatermark value")
1298+
highWm, hwmIsAbs, err := utils.ValidateWatermark(param.Cache_HighWaterMark.GetName(), false)
1299+
if err != nil {
1300+
return err
1301+
}
1302+
if lwmIsAbs == hwmIsAbs && lowWm >= highWm {
1303+
// Use config strings in error to present the configured values (as opposed to whatever conversion we get from validation)
1304+
return errors.Errorf("invalid Cache.HighWaterMark/LowWaterMark values. The high watermark must be greater than the low "+
1305+
"watermark. Got %s (low) and %s (high)", param.Cache_LowWatermark.GetString(), param.Cache_HighWaterMark.GetString())
1306+
}
1307+
}
1308+
1309+
if param.Cache_FilesBaseSize.IsSet() || param.Cache_FilesNominalSize.IsSet() || param.Cache_FilesMaxSize.IsSet() {
1310+
// Must have high/low watermarks
1311+
if !param.Cache_LowWatermark.IsSet() || !param.Cache_HighWaterMark.IsSet() {
1312+
return errors.New("If any of Cache parameters 'FilesBaseSize', 'FilesNominalSize', or 'FilesMaxSize' is set, then Cache.LowWatermark " +
1313+
"and Cache.HighWatermark must also be set")
1314+
}
1315+
1316+
// Further, if one is set, all three must be set
1317+
if !param.Cache_FilesBaseSize.IsSet() || !param.Cache_FilesNominalSize.IsSet() || !param.Cache_FilesMaxSize.IsSet() {
1318+
return errors.New("If any of Cache parameters 'FilesBaseSize', 'FilesNominalSize', or 'FilesMaxSize' is set, all three must be set")
13431319
}
1344-
if lowWmNum >= highWmNum {
1345-
return fmt.Errorf("invalid Cache.HighWaterMark and Cache.LowWatermark values. Cache.HighWaterMark must be greater than Cache.LowWaterMark. Got %s, %s", highWmStr, lowWmStr)
1320+
1321+
var base, nominal, max float64
1322+
var err error
1323+
// Watermark validation will error if these parameters are not absolute, so we can ignore that output
1324+
if base, _, err = utils.ValidateWatermark(param.Cache_FilesBaseSize.GetName(), true); err != nil {
1325+
return err
1326+
}
1327+
if nominal, _, err = utils.ValidateWatermark(param.Cache_FilesNominalSize.GetName(), true); err != nil {
1328+
return err
1329+
}
1330+
if max, _, err = utils.ValidateWatermark(param.Cache_FilesMaxSize.GetName(), true); err != nil {
1331+
return err
1332+
}
1333+
if base >= nominal || nominal >= max {
1334+
return errors.Errorf("invalid Cache.FilesBaseSize/FilesNominalSize/FilesMaxSize values. The base size must be less than the "+
1335+
"nominal size, and the nominal size must be less than the max size. Got %s (base), %s (nominal), and %s (max)",
1336+
param.Cache_FilesBaseSize.GetString(), param.Cache_FilesNominalSize.GetString(), param.Cache_FilesMaxSize.GetString())
1337+
}
1338+
1339+
// File sizes must also be less than the low watermark, but that's not straightforward to check, especially when the cache spread across
1340+
// multiple disks and the low watermark is configured as a relative value. If such bad config is passed, xrootd will fail to startup with
1341+
// a message about what to do, and as much as I'd like to handle that early, I'll leave it to xrootd for now.
1342+
}
1343+
if param.Cache_EnableLotman.GetBool() {
1344+
// pfc.diskusage file directives _must_ be set.
1345+
// We already validate that one means all three, but I'm duplicating that here to make this safer in the case we switch orders
1346+
// in the future.
1347+
if !param.Cache_FilesBaseSize.IsSet() || !param.Cache_FilesNominalSize.IsSet() || !param.Cache_FilesMaxSize.IsSet() ||
1348+
!param.Cache_LowWatermark.IsSet() || !param.Cache_HighWaterMark.IsSet() {
1349+
return errors.New("If Cache.EnableLotman is true, the following Cache parameters must also be set: HighWaterMark, LowWaterMark, " +
1350+
"FilesBaseSize, FilesNominalSize, FilesMaxSize")
13461351
}
13471352
}
13481353

config/config_test.go

Lines changed: 0 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -549,117 +549,6 @@ func TestSetPreferredPrefix(t *testing.T) {
549549
})
550550
}
551551

552-
func TestCheckWatermark(t *testing.T) {
553-
t.Parallel()
554-
555-
t.Run("empty-value", func(t *testing.T) {
556-
ok, num, err := checkWatermark("")
557-
assert.False(t, ok)
558-
assert.Equal(t, int64(0), num)
559-
assert.Error(t, err)
560-
})
561-
562-
t.Run("string-value", func(t *testing.T) {
563-
ok, num, err := checkWatermark("random")
564-
assert.False(t, ok)
565-
assert.Equal(t, int64(0), num)
566-
assert.Error(t, err)
567-
})
568-
569-
t.Run("integer-greater-than-100", func(t *testing.T) {
570-
ok, num, err := checkWatermark("101")
571-
assert.False(t, ok)
572-
assert.Equal(t, int64(0), num)
573-
assert.Error(t, err)
574-
})
575-
576-
t.Run("integer-less-than-0", func(t *testing.T) {
577-
ok, num, err := checkWatermark("-1")
578-
assert.False(t, ok)
579-
assert.Equal(t, int64(0), num)
580-
assert.Error(t, err)
581-
})
582-
583-
t.Run("decimal-fraction-value", func(t *testing.T) {
584-
ok, num, err := checkWatermark("0.55")
585-
assert.False(t, ok)
586-
assert.Equal(t, int64(0), num)
587-
assert.Error(t, err)
588-
})
589-
590-
t.Run("decimal-int-value", func(t *testing.T) {
591-
ok, num, err := checkWatermark("15.55")
592-
assert.False(t, ok)
593-
assert.Equal(t, int64(0), num)
594-
assert.Error(t, err)
595-
})
596-
597-
t.Run("int-value", func(t *testing.T) {
598-
ok, num, err := checkWatermark("55")
599-
assert.True(t, ok)
600-
assert.Equal(t, int64(55), num)
601-
assert.NoError(t, err)
602-
})
603-
604-
t.Run("byte-value-no-unit", func(t *testing.T) {
605-
ok, num, err := checkWatermark("105")
606-
assert.False(t, ok)
607-
assert.Equal(t, int64(0), num)
608-
assert.Error(t, err)
609-
})
610-
611-
t.Run("byte-value-no-value", func(t *testing.T) {
612-
ok, num, err := checkWatermark("k")
613-
assert.False(t, ok)
614-
assert.Equal(t, int64(0), num)
615-
assert.Error(t, err)
616-
})
617-
618-
t.Run("byte-value-wrong-unit", func(t *testing.T) {
619-
ok, num, err := checkWatermark("100K") // Only lower case is accepted
620-
assert.False(t, ok)
621-
assert.Equal(t, int64(0), num)
622-
assert.Error(t, err)
623-
624-
ok, num, err = checkWatermark("100p")
625-
assert.False(t, ok)
626-
assert.Equal(t, int64(0), num)
627-
assert.Error(t, err)
628-
629-
ok, num, err = checkWatermark("100byte")
630-
assert.False(t, ok)
631-
assert.Equal(t, int64(0), num)
632-
assert.Error(t, err)
633-
634-
ok, num, err = checkWatermark("100bits")
635-
assert.False(t, ok)
636-
assert.Equal(t, int64(0), num)
637-
assert.Error(t, err)
638-
})
639-
640-
t.Run("byte-value-correct-unit", func(t *testing.T) {
641-
ok, num, err := checkWatermark("1000k")
642-
assert.True(t, ok)
643-
assert.Equal(t, int64(1000*1024), num)
644-
assert.NoError(t, err)
645-
646-
ok, num, err = checkWatermark("1000m")
647-
assert.True(t, ok)
648-
assert.Equal(t, int64(1000*1024*1024), num)
649-
assert.NoError(t, err)
650-
651-
ok, num, err = checkWatermark("1000g")
652-
assert.True(t, ok)
653-
assert.Equal(t, int64(1000*1024*1024*1024), num)
654-
assert.NoError(t, err)
655-
656-
ok, num, err = checkWatermark("1000t")
657-
assert.True(t, ok)
658-
assert.Equal(t, int64(1000*1024*1024*1024*1024), num)
659-
assert.NoError(t, err)
660-
})
661-
}
662-
663552
func TestInitServerUrl(t *testing.T) {
664553
ctx := testConfigContext(t)
665554

docs/parameters.yaml

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,26 +1326,83 @@ components: ["cache"]
13261326
---
13271327
name: Cache.LowWatermark
13281328
description: |+
1329-
A value of cache disk usage that stops the purging of cached files.
1329+
Whenever the cache initiates file purging, it will attempt to clean files until its cumulative disk
1330+
usages reaches this value. Note that "cache disk usage" is calculated based on the cache's entire set of
1331+
configured disks, not just data directories from those disks.
13301332
13311333
The value should be either a percentage integer of total available disk space (default is 90),
13321334
or a number suffixed by k, m, g, or t. In which case, they must be absolute sizes in k (kilo-),
13331335
m (mega-), g (giga-), or t (tera-) bytes, respectively.
1336+
1337+
For more information, see the [xrootd pfc documentation](https://xrootd.web.cern.ch/doc/dev56/pss_config.pdf) for
1338+
`pfc.diskusage`.
13341339
type: string
13351340
default: 90
13361341
components: ["cache"]
13371342
---
13381343
name: Cache.HighWaterMark
13391344
description: |+
1340-
A value of cache disk usage that triggers the purging of cached files.
1345+
When the cache's disk usage exceeds this value, file purging is triggered. Note that "cache disk usage"
1346+
is calculated based on the cache's entire set of configured disks, not just data directories from those disks.
13411347
13421348
The value should be either a percentage integer of total available disk space (default is 95),
13431349
or a number suffixed by k, m, g, or t. In which case, they must be absolute sizes in k (kilo-),
13441350
m (mega-), g (giga-), or t (tera-) bytes, respectively.
1351+
1352+
For more information, see the [xrootd pfc documentation](https://xrootd.web.cern.ch/doc/dev56/pss_config.pdf) for
1353+
`pfc.diskusage`.
13451354
type: string
13461355
default: 95
13471356
components: ["cache"]
13481357
---
1358+
name: Cache.FilesMaxSize
1359+
description: |+
1360+
A value that sets the maximum cumulative size of files that can be stored in the cache's data directories (specified
1361+
by `Cache.StorageLocation` and `Cache.DataLocations). When either this value or `Cache.HighWaterMark` is exceeded, the
1362+
cache will begin purging files until it reaches the `Cache.FilesNominal` value. If the cache's disk usage is still in
1363+
excess of the `Cache.LowWaterMark`, the cache will continue purging files until it reaches the `Cache.FilesBase` value.
1364+
1365+
Unlike watermark values, this value _must_ be suffixed by a unit of k, m, g, or t, which represent kilobytes, megabytes,
1366+
gigabytes, and terabytes, respectively. All `Cache.Files*Size` parameters must be less than the cache's calculated low
1367+
watermark, which may be configured as a percentage of total disk space from multiple disks.
1368+
1369+
For more information, see the [xrootd pfc documentation](https://xrootd.web.cern.ch/doc/dev56/pss_config.pdf) for
1370+
`pfc.diskusage`.
1371+
type: string
1372+
default: none
1373+
components: ["cache"]
1374+
---
1375+
name: Cache.FilesNominalSize
1376+
description: |+
1377+
A value that sets the "nominal" cumulative size of files that can be stored in the cache's data directory. When files
1378+
in the cache exceed the `Cache.FilesMax` value, or if the cache's overall disk exceeds its `Cache.HighWaterMark` value,
1379+
the cache will begin purging files until it reaches this value. If the cache's disk usage is still in excess of the
1380+
`Cache.LowWaterMark`, the cache will continue purging files until it reaches the `Cache.FilesBase`.
1381+
1382+
Unlike watermark values, this value _must_ be suffixed by a unit of k, m, g, or t, which represent kilobytes, megabytes,
1383+
gigabytes, and terabytes, respectively. All `Cache.Files*Size` parameters must be less than the cache's calculated low
1384+
watermark, which may be configured as a percentage of total disk space from multiple disks.
1385+
1386+
For more information, see the [xrootd pfc documentation](https://xrootd.web.cern.ch/doc/dev56/pss_config.pdf) for
1387+
`pfc.diskusage`.
1388+
type: string
1389+
default: none
1390+
components: ["cache"]
1391+
---
1392+
name: Cache.FilesBaseSize
1393+
description: |+
1394+
A value that sets the "base" cumulative size of files that can be stored in the cache's data directory. This is the
1395+
stopping point for the cache's purging routines.
1396+
1397+
Unlike watermark values, this value _must_ be suffixed by a unit of k, m, g, or t, which represent kilobytes, megabytes,
1398+
gigabytes, and terabytes, respectively.All `Cache.Files*Size` parameters must be less than the cache's calculated low
1399+
watermark, which may be configured as a percentage of total disk space from multiple disks.
1400+
1401+
For more information, see the [xrootd pfc documentation](https://xrootd.web.cern.ch/doc/dev56/pss_config.pdf) for `pfc.diskusage`.
1402+
type: string
1403+
default: none
1404+
components: ["cache"]
1405+
---
13491406
name: Cache.EnableVoms
13501407
description: |+
13511408
Enable X.509 / VOMS-based authentication for the cache. This allows HTTP clients

lotman/lotman.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ import (
3333
"github.com/pelicanplatform/pelican/server_structs"
3434
)
3535

36+
// Minimal re-definition of PurgePolicy so that things compile on non-Linux platforms.
37+
// We may use a different approach someday so we don't need multiple definitions of this struct
38+
// but for now I think this is okay...
39+
type PurgePolicy struct {
40+
PurgeOrder []string
41+
}
42+
3643
func RegisterLotman(ctx context.Context, router *gin.RouterGroup) {
3744
log.Warningln("LotMan is not supported on this platform. Skipping...")
3845
}
@@ -41,3 +48,8 @@ func InitLotman(adsFromFed []server_structs.NamespaceAdV2) bool {
4148
log.Warningln("LotMan is not supported on this platform. Skipping...")
4249
return false
4350
}
51+
52+
func GetPolicyMap() (map[string]PurgePolicy, error) {
53+
log.Warningln("LotMan is not supported on this platform. Skipping...")
54+
return map[string]PurgePolicy{}, nil
55+
}

0 commit comments

Comments
 (0)