Skip to content

Commit fd9511e

Browse files
committed
Switch to using existing version detection code
This also tweaks the existing version detection code to make sure it is only run once by the binary: seems safe to assume XRootD versions won't change.
1 parent bd20971 commit fd9511e

5 files changed

Lines changed: 78 additions & 52 deletions

File tree

xrootd/plugin_check.go

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,15 @@ import (
2323
"os"
2424
"os/exec"
2525
"path/filepath"
26-
"regexp"
2726
"runtime"
2827
"sort"
2928
"strings"
3029
"sync"
3130

3231
"github.com/pkg/errors"
33-
log "github.com/sirupsen/logrus"
3432
)
3533

3634
var (
37-
xrootdVersionOnce sync.Once
38-
xrootdVersion string
39-
4035
pluginSearchPathsOnce sync.Once
4136
pluginSearchPaths []string
4237
)
@@ -326,30 +321,6 @@ func parsePluginConfigFile(filePath string) (string, bool) {
326321
return libPath, enabled
327322
}
328323

329-
// getXRootDVersion runs 'xrootd -v' and extracts the major version number
330-
func getXRootDVersion() string {
331-
xrootdVersionOnce.Do(func() {
332-
cmd := exec.Command("xrootd", "-v")
333-
output, err := cmd.CombinedOutput()
334-
if err != nil {
335-
log.Debugf("Failed to get xrootd version: %v", err)
336-
xrootdVersion = ""
337-
return
338-
}
339-
340-
// Parse version from output (e.g., "XRootD v5.7.1" or "v6.0.0")
341-
re := regexp.MustCompile(`v(\d+)\.\d+\.\d+`)
342-
matches := re.FindStringSubmatch(string(output))
343-
if len(matches) > 1 {
344-
xrootdVersion = matches[1] // Return major version like "5" or "6"
345-
} else {
346-
xrootdVersion = ""
347-
}
348-
})
349-
350-
return xrootdVersion
351-
}
352-
353324
// getPluginVariants returns a list of possible plugin filenames for a given base name.
354325
// XRootD automatically adds the major version to plugin names, so libXrdHttpPelican.so
355326
// may be libXrdHttpPelican-5.so or libXrdHttpPelican-6.so on disk.
@@ -374,7 +345,7 @@ func getPluginVariants(baseName string) []string {
374345
variants = append(variants, nameWithoutExt+ext)
375346

376347
// Try to get the actual XRootD version
377-
version := getXRootDVersion()
348+
version := GetXrootdMajorVersion()
378349
if version != "" {
379350
// Use the detected version
380351
variants = append(variants, nameWithoutExt+"-"+version+ext)

xrootd/plugin_check_test.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -448,13 +448,3 @@ func TestGetXRootDRPaths(t *testing.T) {
448448
assert.True(t, filepath.IsAbs(path), "All paths should be absolute")
449449
}
450450
}
451-
452-
func TestGetXRootDVersion(t *testing.T) {
453-
// This test checks that getXRootDVersion doesn't panic
454-
// It may return empty if xrootd is not in PATH, which is okay
455-
version := getXRootDVersion()
456-
if version != "" {
457-
// If we got a version, it should be a number
458-
assert.Regexp(t, `^\d+$`, version, "Version should be a single digit")
459-
}
460-
}

xrootd/version.go

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ package xrootd
2222

2323
import (
2424
"os/exec"
25+
"regexp"
2526
"strings"
27+
"sync"
2628

2729
"github.com/hashicorp/go-version"
2830
"github.com/pkg/errors"
@@ -33,19 +35,65 @@ import (
3335
// This version must be kept in sync with the version specified in .goreleaser.yml (two locations: RPM and DEB dependencies).
3436
const MinXrootdVersion = "5.8.2"
3537

38+
var (
39+
xrootdVersionOnce sync.Once
40+
xrootdVersionOutput string
41+
xrootdVersionErr error
42+
)
43+
44+
// ResetXrootdVersionForTesting resets the cached XRootD version output.
45+
// This should only be used in tests.
46+
func ResetXrootdVersionForTesting() {
47+
xrootdVersionOnce = sync.Once{}
48+
xrootdVersionOutput = ""
49+
xrootdVersionErr = nil
50+
}
51+
52+
// getXrootdVersionOutput runs 'xrootd -v' once and caches the result.
53+
// Subsequent calls return the cached output without re-executing the command.
54+
func getXrootdVersionOutput() (string, error) {
55+
xrootdVersionOnce.Do(func() {
56+
// Execute xrootd -v to get version information
57+
// Note: xrootd outputs version to stderr, not stdout
58+
cmd := exec.Command("xrootd", "-v")
59+
output, err := cmd.CombinedOutput()
60+
if err != nil {
61+
xrootdVersionErr = err
62+
return
63+
}
64+
xrootdVersionOutput = strings.TrimSpace(string(output))
65+
})
66+
return xrootdVersionOutput, xrootdVersionErr
67+
}
68+
69+
// GetXrootdMajorVersion returns the major version number of the installed XRootD
70+
// (e.g., "5" or "6"). Returns an empty string if the version cannot be determined.
71+
// The result is cached after the first call.
72+
func GetXrootdMajorVersion() string {
73+
output, err := getXrootdVersionOutput()
74+
if err != nil || output == "" {
75+
return ""
76+
}
77+
78+
re := regexp.MustCompile(`v?(\d+)\.\d+\.\d+`)
79+
matches := re.FindStringSubmatch(output)
80+
if len(matches) > 1 {
81+
return matches[1]
82+
}
83+
return ""
84+
}
85+
3686
/*
3787
* CheckXrootdVersion checks if the installed XRootD version meets the minimum requirement.
3888
* It executes 'xrootd -v' to retrieve the version and compares it against MinXrootdVersion.
89+
* The xrootd binary is only invoked once; subsequent calls use the cached result.
3990
* Returns an error if:
4091
* - The xrootd binary is not found in PATH
4192
* - The version cannot be determined
4293
* - The version is below the minimum requirement
4394
*/
4495
func CheckXrootdVersion() error {
45-
// Execute xrootd -v to get version information
46-
// Note: xrootd outputs version to stderr, not stdout
47-
cmd := exec.Command("xrootd", "-v")
48-
output, err := cmd.CombinedOutput()
96+
output, err := getXrootdVersionOutput()
4997
if err != nil {
5098
// Check if this is a "command not found" error
5199
if exitErr, ok := err.(*exec.ExitError); ok {
@@ -60,20 +108,19 @@ func CheckXrootdVersion() error {
60108
}
61109

62110
// Parse the version output
63-
versionStr := strings.TrimSpace(string(output))
64-
log.Debugf("XRootD version output: %s", versionStr)
111+
log.Debugf("XRootD version output: %s", output)
65112

66113
// Remove leading 'v' if present (e.g., "v5.8.2" -> "5.8.2")
67-
versionStr = strings.TrimPrefix(versionStr, "v")
114+
versionStr := strings.TrimPrefix(output, "v")
68115

69116
// Parse the version string
70-
xrootdVersion, err := version.NewVersion(versionStr)
117+
xrootdVer, err := version.NewVersion(versionStr)
71118
if err != nil {
72119
return errors.Wrapf(err, "failed to parse XRootD version string '%s'. Please ensure XRootD is properly installed", versionStr)
73120
}
74121

75122
// Normalize to core version (strips pre-release suffixes like -rc1, +git123)
76-
xrootdVersion = xrootdVersion.Core()
123+
xrootdVer = xrootdVer.Core()
77124

78125
// Parse the minimum required version
79126
requiredVersion, err := version.NewVersion(MinXrootdVersion)
@@ -83,13 +130,13 @@ func CheckXrootdVersion() error {
83130
}
84131

85132
// Compare versions
86-
if xrootdVersion.LessThan(requiredVersion) {
133+
if xrootdVer.LessThan(requiredVersion) {
87134
return errors.Errorf("XRootD version %s is insufficient (minimum required: %s). "+
88135
"Please upgrade XRootD to version %s or later. "+
89136
"This requirement is necessary for proper operation of Pelican Cache and Origin servers.",
90-
xrootdVersion.String(), MinXrootdVersion, MinXrootdVersion)
137+
xrootdVer.String(), MinXrootdVersion, MinXrootdVersion)
91138
}
92139

93-
log.Debugf("XRootD version check passed: %s >= %s", xrootdVersion.String(), MinXrootdVersion)
140+
log.Debugf("XRootD version check passed: %s >= %s", xrootdVer.String(), MinXrootdVersion)
94141
return nil
95142
}

xrootd/version_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ func TestCheckXrootdVersion(t *testing.T) {
116116

117117
for _, tt := range tests {
118118
t.Run(tt.name, func(t *testing.T) {
119+
// Reset cached version so each subtest gets a fresh detection
120+
ResetXrootdVersionForTesting()
121+
119122
// Create a mock xrootd binary
120123
tmpDir := createMockXrootd(t, tt.version, tt.exitCode)
121124

@@ -146,6 +149,9 @@ func TestCheckXrootdVersion(t *testing.T) {
146149

147150
// TestCheckXrootdVersion_BinaryNotFound tests the scenario where xrootd is not installed
148151
func TestCheckXrootdVersion_BinaryNotFound(t *testing.T) {
152+
// Reset cached version for this test
153+
ResetXrootdVersionForTesting()
154+
149155
// Create an empty temporary directory
150156
tmpDir := t.TempDir()
151157

@@ -187,6 +193,9 @@ func TestCheckXrootdVersion_InvalidVersionFormat(t *testing.T) {
187193

188194
for _, tt := range tests {
189195
t.Run(tt.name, func(t *testing.T) {
196+
// Reset cached version so each subtest gets a fresh detection
197+
ResetXrootdVersionForTesting()
198+
190199
// Create a mock xrootd binary
191200
tmpDir := createMockXrootd(t, tt.version, 0)
192201

@@ -211,6 +220,9 @@ func TestCheckXrootdVersion_InvalidVersionFormat(t *testing.T) {
211220

212221
// TestCheckXrootdVersion_RealBinary tests with the actual xrootd binary if available
213222
func TestCheckXrootdVersion_RealBinary(t *testing.T) {
223+
// Reset cached version for this test
224+
ResetXrootdVersionForTesting()
225+
214226
// Check if xrootd is actually available
215227
_, err := exec.LookPath("xrootd")
216228
if err != nil {

xrootd/version_windows.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020

2121
package xrootd
2222

23+
// ResetXrootdVersionForTesting is a no-op on Windows since XRootD is not supported on this platform.
24+
func ResetXrootdVersionForTesting() {}
25+
26+
// GetXrootdMajorVersion is a no-op on Windows since XRootD is not supported on this platform.
27+
func GetXrootdMajorVersion() string { return "" }
28+
2329
// CheckXrootdVersion is a no-op on Windows since XRootD is not supported on this platform.
2430
// Cache and origin servers are not available on Windows.
2531
func CheckXrootdVersion() error {

0 commit comments

Comments
 (0)