@@ -41,6 +41,7 @@ import (
4141 "github.com/pelicanplatform/pelican/server_utils"
4242 "github.com/pelicanplatform/pelican/token"
4343 "github.com/pelicanplatform/pelican/token_scopes"
44+ "github.com/pelicanplatform/pelican/xrootd"
4445)
4546
4647const (
@@ -62,22 +63,13 @@ func InitSelfTestDir() error {
6263 }
6364
6465 basePath := param .Cache_NamespaceLocation .GetString ()
65- pelicanMonPath := filepath .Join (basePath , "/pelican" )
66- monitoringPath := filepath .Join (pelicanMonPath , "/monitoring" )
67- selfTestPath := filepath .Join (monitoringPath , "/selfTest" )
68- err = os .MkdirAll (selfTestPath , 0700 )
66+ selfTestPath := filepath .Join (basePath , selfTestDir )
67+ err = config .MkdirAll (selfTestPath , 0750 , uid , gid )
6968 if err != nil {
7069 return errors .Wrap (err , "Fail to create directory for the self-test" )
7170 }
72- if err = os .Chown (pelicanMonPath , uid , gid ); err != nil {
73- return errors .Wrapf (err , "Unable to change ownership of self-test /pelican directory %v to desired daemon gid %v" , monitoringPath , gid )
74- }
75- if err = os .Chown (monitoringPath , uid , gid ); err != nil {
76- return errors .Wrapf (err , "Unable to change ownership of self-test /pelican/monitoring directory %v to desired daemon gid %v" , monitoringPath , gid )
77- }
78- if err = os .Chown (selfTestPath , uid , gid ); err != nil {
79- return errors .Wrapf (err , "Unable to change ownership of self-test /pelican/monitoring directory %v to desired daemon gid %v" , monitoringPath , gid )
80- }
71+ log .Debugf ("Created cache self-test directory at %s" , selfTestPath )
72+
8173 return nil
8274}
8375
@@ -169,6 +161,94 @@ func generateTestFile() (string, error) {
169161 return baseUrl .String (), nil
170162}
171163
164+ // generateTestFileViaPlugin creates a test file and its .cinfo file in a temp location (birthplace),
165+ // then copies them to the selfTestDir using the xrdhttp-pelican plugin. This function is used
166+ // when drop privileges is enabled, as the pelican server is running as an unprivileged user
167+ // and cannot directly create files in the selfTestDir.
168+ func generateTestFileViaPlugin () (string , error ) {
169+ // Create a temp directory own by pelican user to bypass privilege restrictions, named "birthplace"
170+ selfTestBirthplace := filepath .Join (param .Monitoring_DataLocation .GetString (), "selfTest" )
171+ err := os .MkdirAll (selfTestBirthplace , 0750 )
172+ if err != nil {
173+ return "" , errors .Wrap (err , "failed to create selftest directory" )
174+ }
175+
176+ // Create a test file and its cinfo in the birthplace
177+ testFileBytes := []byte (selfTestBody )
178+ fileSize := len (testFileBytes )
179+ cinfo := cache.Cinfo {
180+ Store : cache.Store {
181+ FileSize : int64 (fileSize ),
182+ CreationTime : time .Now ().Unix (),
183+ Status : 2 ,
184+ },
185+ }
186+ cinfoBytes , err := cinfo .Serialize ()
187+ if err != nil {
188+ return "" , err
189+ }
190+
191+ file , err := os .CreateTemp (selfTestBirthplace , selfTestPrefix + "*.txt" )
192+ if err != nil {
193+ return "" , errors .Wrap (err , "failed to create test file" )
194+ }
195+ cinfoFile , err := os .Create (file .Name () + ".cinfo" )
196+ if err != nil {
197+ return "" , errors .Wrap (err , "failed to create test file cinfo" )
198+ }
199+ defer func () {
200+ file .Close ()
201+ cinfoFile .Close ()
202+ // Delete the test file and its cinfo in the birthplace
203+ if err := os .Remove (file .Name ()); err != nil {
204+ log .Warningf ("Failed to remove test file %s: %v" , file .Name (), err )
205+ }
206+ if err := os .Remove (cinfoFile .Name ()); err != nil {
207+ log .Warningf ("Failed to remove test file cinfo %s: %v" , cinfoFile .Name (), err )
208+ }
209+ }()
210+
211+ // Write test data and cinfo to the files
212+ if _ , err := file .Write (testFileBytes ); err != nil {
213+ return "" , errors .Wrapf (err , "failed to write test content to self-test file %s" , file .Name ())
214+ }
215+ if _ , err := cinfoFile .Write (cinfoBytes ); err != nil {
216+ return "" , errors .Wrapf (err , "failed to write cinfo content to self-test cinfo %s" , cinfoFile .Name ())
217+ }
218+
219+ // After writing the test content to the file, the file pointer remains at the end.
220+ // Seek back to the beginning of the file so that the copy operation reads from the start.
221+ if _ , err := file .Seek (0 , io .SeekStart ); err != nil {
222+ return "" , errors .Wrap (err , "failed to seek to beginning of test file" )
223+ }
224+ if _ , err := cinfoFile .Seek (0 , io .SeekStart ); err != nil {
225+ return "" , errors .Wrap (err , "failed to seek to beginning of cinfo file" )
226+ }
227+
228+ // Transplant the test file and cinfo from birthplace to the selfTestDir via xrdhttp-pelican plugin
229+ if err = xrootd .SelfTestFileCopy (4 , file ); err != nil {
230+ return "" , errors .Wrap (err , "failed to copy the test file to the self-test directory" )
231+ }
232+ if err = xrootd .SelfTestFileCopy (5 , cinfoFile ); err != nil {
233+ return "" , errors .Wrap (err , "failed to copy the test cinfo file to the self-test directory" )
234+ }
235+
236+ // Construct and return the URL of the copied test file in the selfTestDir
237+ cachePort := param .Cache_Port .GetInt ()
238+ baseUrlStr := fmt .Sprintf ("https://%s:%d" , param .Server_Hostname .GetString (), cachePort )
239+ baseUrl , err := url .Parse (baseUrlStr )
240+ if err != nil {
241+ return "" , errors .Wrap (err , "failed to validate the base url for self-test download" )
242+ }
243+ // This is for web URL path, do not use filepath pkg.
244+ // The file name of the test file is always the same in the selfTestDir,
245+ // no matter what's the file name in the birthplace.
246+ extFilePath := path .Join (selfTestDir , selfTestPrefix + "cache-server.txt" )
247+ baseUrl .Path = extFilePath
248+
249+ return baseUrl .String (), nil
250+ }
251+
172252func generateFileTestScitoken () (string , error ) {
173253 issuerUrl := param .Server_ExternalWebUrl .GetString ()
174254 if issuerUrl == "" { // if both are empty, then error
@@ -227,6 +307,14 @@ func downloadTestFile(ctx context.Context, fileUrl string) error {
227307}
228308
229309func deleteTestFile (fileUrlStr string ) error {
310+ // If drop privileges is enabled, the test file at selfTestDir will be overwritten by the next self-test,
311+ // because the test file and cinfo at selfTestDir always have the same name.
312+ // Also, the test file and cinfo in their birthplace were deleted right after generateTestFileViaPlugin,
313+ // so this function can be skipped.
314+ if param .Server_DropPrivileges .GetBool () {
315+ return nil
316+ }
317+
230318 basePath := param .Cache_NamespaceLocation .GetString ()
231319 fileUrl , err := url .Parse (fileUrlStr )
232320 if err != nil {
@@ -250,18 +338,30 @@ func deleteTestFile(fileUrlStr string) error {
250338}
251339
252340func runSelfTest (ctx context.Context ) (bool , error ) {
253- fileUrl , err := generateTestFile ()
254- if err != nil {
255- return false , errors .Wrap (err , "self-test failed when generating the file" )
341+ var fileUrl string
342+ var err error
343+ if param .Server_DropPrivileges .GetBool () {
344+ fileUrl , err = generateTestFileViaPlugin ()
345+ if err != nil {
346+ return false , errors .Wrap (err , "self-test failed when generating the file" )
347+ }
348+ } else {
349+ fileUrl , err = generateTestFile ()
350+ if err != nil {
351+ return false , errors .Wrap (err , "self-test failed when generating the file" )
352+ }
256353 }
354+
257355 err = downloadTestFile (ctx , fileUrl )
258356 if err != nil {
357+ log .Warningf ("Self-test download failed for file %s; err: %v" , fileUrl , err )
259358 errDel := deleteTestFile (fileUrl )
260359 if errDel != nil {
261360 return false , errors .Wrap (errDel , "self-test failed during delete" )
262361 }
263362 return false , errors .Wrap (err , "self-test failed during download. File is cleaned up at " + fileUrl )
264363 }
364+
265365 err = deleteTestFile (fileUrl )
266366 if err != nil {
267367 return false , errors .Wrap (err , "self-test failed during delete" )
@@ -304,22 +404,23 @@ func doSelfMonitorOrigin(ctx context.Context) {
304404// Start self-test monitoring of the origin/cache. This will upload, download, and delete
305405// a generated filename every 15 seconds to the local origin. On failure, it will
306406// set the xrootd component's status to critical.
307- func PeriodicCacheSelfTest (ctx context.Context , ergp * errgroup.Group , isOrigin bool ) {
407+ func PeriodicSelfTest (ctx context.Context , ergp * errgroup.Group , isOrigin bool ) {
408+ customInterval := param .Cache_SelfTestInterval .GetDuration ()
308409 doSelfMonitor := doSelfMonitorCache
309410 if isOrigin {
310411 doSelfMonitor = doSelfMonitorOrigin
412+ customInterval = param .Origin_SelfTestInterval .GetDuration ()
311413 }
312414
313- customInterval := param .Cache_SelfTestInterval .GetDuration ()
314415 if customInterval == 0 {
315416 customInterval = 15 * time .Second
316- log .Error ("Invalid config value: Cache.SelfTestInterval is 0. Fallback to 15s." )
417+ log .Error ("Invalid config value: both Origin.SelfTestInterval and Cache.SelfTestInterval are 0. Fallback to 15s." )
317418 }
318419 ticker := time .NewTicker (customInterval )
319- defer ticker .Stop ()
320420 firstRound := time .After (5 * time .Second )
321421
322422 ergp .Go (func () error {
423+ defer ticker .Stop ()
323424 for {
324425 select {
325426 case <- firstRound :
0 commit comments