Skip to content

Commit d2810d4

Browse files
committed
Improve error message advice by including missing package names
Add additional checks for plugins that can depend on configuration.
1 parent 954fac0 commit d2810d4

2 files changed

Lines changed: 225 additions & 30 deletions

File tree

xrootd/plugin_check.go

Lines changed: 94 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -387,41 +387,93 @@ func checkPluginExists(pluginName string, includeClientPaths bool) bool {
387387
return false
388388
}
389389

390+
// pluginPackageMap maps plugin base names (without extension) to the RPM package that provides them.
391+
// These were determined from an OSG EL9 environment; names may differ in other distros but
392+
// should be correct for the primary Pelican deployment targets.
393+
var pluginPackageMap = map[string]string{
394+
"libXrdHttpPelican": "xrdhttp-pelican",
395+
"libXrdS3": "xrootd-s3-http",
396+
"libXrdClPelican": "xrdcl-pelican",
397+
"libXrdThrottle": "xrootd-server-libs",
398+
"libXrdMacaroons": "xrootd-server-libs",
399+
"libXrdMultiuser": "xrootd-multiuser",
400+
"libXrdOssPosc": "xrootd-s3-http",
401+
"libXrdHTTPServer": "xrootd-s3-http",
402+
"libXrdOssGlobus": "xrootd-s3-http",
403+
"libXrdPurgeLotMan": "xrootd-lotman",
404+
}
405+
406+
// pluginBaseName strips the extension from a plugin filename (e.g. "libXrdS3.so" -> "libXrdS3").
407+
func pluginBaseName(name string) string {
408+
for _, ext := range []string{".so", ".dylib"} {
409+
name = strings.TrimSuffix(name, ext)
410+
}
411+
return name
412+
}
413+
414+
// checkAndAppendMissing checks if a plugin exists and appends it to the missing list if not found.
415+
func checkAndAppendMissing(missingPlugins *[]string, pluginName string, includeClientPaths bool) {
416+
if !checkPluginExists(pluginName, includeClientPaths) {
417+
*missingPlugins = append(*missingPlugins, pluginName)
418+
}
419+
}
420+
390421
// ValidateRequiredPlugins checks for the existence of required XRootD plugins based on the configuration.
391422
// It returns an error with a detailed message if any required plugin is missing.
392423
func ValidateRequiredPlugins(isOrigin bool, xrdConfig *XrootdConfig) error {
393424
missingPlugins := []string{}
394425

395426
if isOrigin {
396-
// Check for libXrdHttpPelican if drop privileges is enabled
427+
// libXrdHttpPelican is required when drop privileges is enabled
397428
if xrdConfig.Server.DropPrivileges {
398-
if !checkPluginExists("libXrdHttpPelican.so", false) {
399-
missingPlugins = append(missingPlugins, "libXrdHttpPelican.so")
400-
}
429+
checkAndAppendMissing(&missingPlugins, "libXrdHttpPelican.so", false)
430+
}
431+
432+
// libXrdMacaroons is needed when macaroons are enabled
433+
if xrdConfig.Origin.EnableMacaroons {
434+
checkAndAppendMissing(&missingPlugins, "libXrdMacaroons.so", false)
435+
}
436+
437+
// libXrdThrottle is needed when concurrency is configured
438+
if xrdConfig.Origin.Concurrency > 0 {
439+
checkAndAppendMissing(&missingPlugins, "libXrdThrottle.so", false)
401440
}
402441

403-
// Check for libXrdS3 if using S3 storage type
404-
if xrdConfig.Origin.StorageType == "s3" {
405-
if !checkPluginExists("libXrdS3.so", false) {
406-
missingPlugins = append(missingPlugins, "libXrdS3.so")
442+
switch xrdConfig.Origin.StorageType {
443+
case "posix":
444+
// libXrdMultiuser is needed for multiuser mode
445+
if xrdConfig.Origin.Multiuser {
446+
checkAndAppendMissing(&missingPlugins, "libXrdMultiuser.so", false)
407447
}
448+
// libXrdOssPosc is needed for atomic uploads
449+
if xrdConfig.Origin.EnableAtomicUploads {
450+
checkAndAppendMissing(&missingPlugins, "libXrdOssPosc.so", false)
451+
}
452+
case "s3":
453+
checkAndAppendMissing(&missingPlugins, "libXrdS3.so", false)
454+
case "https":
455+
checkAndAppendMissing(&missingPlugins, "libXrdHTTPServer.so", false)
456+
case "globus":
457+
checkAndAppendMissing(&missingPlugins, "libXrdHTTPServer.so", false)
458+
checkAndAppendMissing(&missingPlugins, "libXrdOssGlobus.so", false)
408459
}
409460
} else {
410461
// Cache-specific checks
411-
// Check for libXrdHttpPelican if drop privileges is enabled
412-
if xrdConfig.Server.DropPrivileges {
413-
if !checkPluginExists("libXrdHttpPelican.so", false) {
414-
missingPlugins = append(missingPlugins, "libXrdHttpPelican.so")
415-
}
416-
}
462+
// libXrdHttpPelican is always required for caches (unconditional in cache template)
463+
checkAndAppendMissing(&missingPlugins, "libXrdHttpPelican.so", false)
417464

418-
// Check for client plugin - this is needed for cache servers to support pelican:// URLs.
419-
// The cache configuration writes client plugin settings to the cache-client.plugins.d directory
420-
// (see CheckCacheEnv in xrootd_config.go), which configures XRootD to use libXrdClPelican.so
421-
// for handling pelican:// protocol requests.
465+
// libXrdClPelican is needed for cache servers to support pelican:// URLs.
422466
// Include client plugin configuration paths for this check.
423-
if !checkPluginExists("libXrdClPelican.so", true) {
424-
missingPlugins = append(missingPlugins, "libXrdClPelican.so")
467+
checkAndAppendMissing(&missingPlugins, "libXrdClPelican.so", true)
468+
469+
// libXrdPurgeLotMan is needed when Lotman is enabled
470+
if xrdConfig.Cache.LotmanCfg.Enabled {
471+
checkAndAppendMissing(&missingPlugins, "libXrdPurgeLotMan.so", false)
472+
}
473+
474+
// libXrdThrottle is needed when concurrency is configured
475+
if xrdConfig.Cache.Concurrency > 0 {
476+
checkAndAppendMissing(&missingPlugins, "libXrdThrottle.so", false)
425477
}
426478
}
427479

@@ -431,14 +483,34 @@ func ValidateRequiredPlugins(isOrigin bool, xrdConfig *XrootdConfig) error {
431483
if runtime.GOOS == "darwin" {
432484
envVars = "DYLD_LIBRARY_PATH, or DYLD_FALLBACK_LIBRARY_PATH"
433485
}
486+
487+
// Build per-plugin install advice, deduplicating packages
488+
seenPkgs := make(map[string]bool)
489+
var pkgAdvice []string
490+
for _, plugin := range missingPlugins {
491+
base := pluginBaseName(plugin)
492+
if pkg, ok := pluginPackageMap[base]; ok && !seenPkgs[pkg] {
493+
seenPkgs[pkg] = true
494+
pkgAdvice = append(pkgAdvice, pkg)
495+
}
496+
}
497+
498+
var installHint string
499+
if len(pkgAdvice) > 0 {
500+
installHint = "\nThe missing plugin(s) are provided by the following package(s): " +
501+
strings.Join(pkgAdvice, ", ")
502+
}
503+
434504
return errors.Errorf(
435505
"Required XRootD plugin(s) not found: %s\n"+
436506
"Please install the missing plugin(s) in one of the following directories:\n %s\n"+
437-
"Or set the %s environment variable to include the plugin location.\n"+
438-
"Note: XRootD may add a version suffix to plugin names (e.g., libXrdHttpPelican-5.so for XRootD 5.x)",
507+
"Or set the %s environment variable to include the plugin location.%s\n"+
508+
"When searching on disk, XRootD version suffixes are automatically added to the filename\n"+
509+
"(e.g., libXrdHttpPelican.so in config is expected to be libXrdHttpPelican-5.so on disk)",
439510
strings.Join(missingPlugins, ", "),
440511
strings.Join(searchPaths, "\n "),
441512
envVars,
513+
installHint,
442514
)
443515
}
444516

xrootd/plugin_check_test.go

Lines changed: 131 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -403,23 +403,146 @@ func TestValidateRequiredPlugins(t *testing.T) {
403403
}
404404
})
405405

406-
t.Run("CacheWithDropPrivileges", func(t *testing.T) {
406+
t.Run("OriginWithHttpsStorage", func(t *testing.T) {
407+
xrdConfig := &XrootdConfig{
408+
Origin: OriginConfig{
409+
StorageType: "https",
410+
},
411+
}
412+
413+
err := ValidateRequiredPlugins(true, xrdConfig)
414+
if err != nil {
415+
assert.Contains(t, err.Error(), "libXrdHTTPServer.so")
416+
}
417+
})
418+
419+
t.Run("OriginWithGlobusStorage", func(t *testing.T) {
420+
xrdConfig := &XrootdConfig{
421+
Origin: OriginConfig{
422+
StorageType: "globus",
423+
},
424+
}
425+
426+
err := ValidateRequiredPlugins(true, xrdConfig)
427+
if err != nil {
428+
assert.Contains(t, err.Error(), "libXrdHTTPServer.so")
429+
assert.Contains(t, err.Error(), "libXrdOssGlobus.so")
430+
}
431+
})
432+
433+
t.Run("OriginWithMacaroons", func(t *testing.T) {
434+
xrdConfig := &XrootdConfig{
435+
Origin: OriginConfig{
436+
StorageType: "posix",
437+
EnableMacaroons: true,
438+
},
439+
}
440+
441+
err := ValidateRequiredPlugins(true, xrdConfig)
442+
if err != nil {
443+
assert.Contains(t, err.Error(), "libXrdMacaroons.so")
444+
}
445+
})
446+
447+
t.Run("OriginWithConcurrency", func(t *testing.T) {
448+
xrdConfig := &XrootdConfig{
449+
Origin: OriginConfig{
450+
StorageType: "posix",
451+
Concurrency: 4,
452+
},
453+
}
454+
455+
err := ValidateRequiredPlugins(true, xrdConfig)
456+
if err != nil {
457+
assert.Contains(t, err.Error(), "libXrdThrottle.so")
458+
}
459+
})
460+
461+
t.Run("OriginWithMultiuser", func(t *testing.T) {
462+
xrdConfig := &XrootdConfig{
463+
Origin: OriginConfig{
464+
StorageType: "posix",
465+
Multiuser: true,
466+
},
467+
}
468+
469+
err := ValidateRequiredPlugins(true, xrdConfig)
470+
if err != nil {
471+
assert.Contains(t, err.Error(), "libXrdMultiuser.so")
472+
}
473+
})
474+
475+
t.Run("OriginWithAtomicUploads", func(t *testing.T) {
476+
xrdConfig := &XrootdConfig{
477+
Origin: OriginConfig{
478+
StorageType: "posix",
479+
EnableAtomicUploads: true,
480+
},
481+
}
482+
483+
err := ValidateRequiredPlugins(true, xrdConfig)
484+
if err != nil {
485+
assert.Contains(t, err.Error(), "libXrdOssPosc.so")
486+
}
487+
})
488+
489+
t.Run("CacheAlwaysRequiresHttpPelican", func(t *testing.T) {
490+
// libXrdHttpPelican should always be checked for caches,
491+
// regardless of DropPrivileges setting
407492
xrdConfig := &XrootdConfig{
408493
Server: ServerConfig{
409-
DropPrivileges: true,
494+
DropPrivileges: false,
410495
},
411496
Cache: CacheConfig{},
412497
}
413498

414499
err := ValidateRequiredPlugins(false, xrdConfig)
415-
// This may fail if plugins are not installed, which is expected
416500
if err != nil {
417501
assert.Contains(t, err.Error(), "Required XRootD plugin(s) not found")
418-
// Should check for both libXrdHttpPelican and libXrdClPelican
419-
assert.True(t,
420-
strings.Contains(err.Error(), "libXrdHttpPelican.so") ||
421-
strings.Contains(err.Error(), "libXrdClPelican.so"),
422-
)
502+
assert.Contains(t, err.Error(), "libXrdHttpPelican.so")
503+
}
504+
})
505+
506+
t.Run("CacheWithLotman", func(t *testing.T) {
507+
xrdConfig := &XrootdConfig{
508+
Cache: CacheConfig{
509+
LotmanCfg: LotmanCfg{
510+
Enabled: true,
511+
},
512+
},
513+
}
514+
515+
err := ValidateRequiredPlugins(false, xrdConfig)
516+
if err != nil {
517+
assert.Contains(t, err.Error(), "libXrdPurgeLotMan.so")
518+
}
519+
})
520+
521+
t.Run("CacheWithConcurrency", func(t *testing.T) {
522+
xrdConfig := &XrootdConfig{
523+
Cache: CacheConfig{
524+
Concurrency: 4,
525+
},
526+
}
527+
528+
err := ValidateRequiredPlugins(false, xrdConfig)
529+
if err != nil {
530+
assert.Contains(t, err.Error(), "libXrdThrottle.so")
531+
}
532+
})
533+
534+
t.Run("ErrorMessageFormat", func(t *testing.T) {
535+
xrdConfig := &XrootdConfig{
536+
Cache: CacheConfig{},
537+
}
538+
539+
err := ValidateRequiredPlugins(false, xrdConfig)
540+
if err != nil {
541+
errMsg := err.Error()
542+
// Should mention the specific packages that provide the plugins
543+
assert.Contains(t, errMsg, "xrdhttp-pelican")
544+
assert.Contains(t, errMsg, "xrdcl-pelican")
545+
assert.NotContains(t, errMsg, "Note:")
423546
}
424547
})
425548
}

0 commit comments

Comments
 (0)