@@ -10,19 +10,14 @@ import (
1010 "github.com/aws/aws-sdk-go/service/s3"
1111 . "github.com/onsi/gomega"
1212 corev1 "k8s.io/api/core/v1"
13- k8serrors "k8s.io/apimachinery/pkg/api/errors"
14- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1513
1614 . "github.com/ray-project/kuberay/ray-operator/test/support"
1715
1816 "github.com/ray-project/kuberay/historyserver/pkg/utils"
1917 . "github.com/ray-project/kuberay/historyserver/test/support"
2018)
2119
22- const (
23- LiveSessionName = "live"
24- EndpointLogFile = "/api/v0/logs/file"
25- )
20+ const LiveSessionName = "live"
2621
2722func TestHistoryServer (t * testing.T ) {
2823 // Share a single S3 client among subtests.
@@ -192,15 +187,6 @@ func getClusterFromList(test Test, g *WithT, historyServerURL, clusterName, name
192187}
193188
194189// testLogFileEndpointLiveCluster verifies that the history server can fetch log files from a live cluster.
195- //
196- // The test case follows these steps:
197- // 1. Prepare test environment by applying a Ray cluster
198- // 2. Submit a Ray job to the existing cluster
199- // 3. Apply History Server and get its URL
200- // 4. Get the cluster info from the list
201- // 5. Verify that the history server can fetch log content (raylet.out)
202- // 6. Verify that the history server rejects path traversal attempts
203- // 7. Delete S3 bucket to ensure test isolation
204190func testLogFileEndpointLiveCluster (test Test , g * WithT , namespace * corev1.Namespace , s3Client * s3.S3 ) {
205191 rayCluster := PrepareTestEnv (test , g , namespace , s3Client )
206192 ApplyRayJobAndWaitForCompletion (test , g , namespace , rayCluster )
@@ -212,70 +198,25 @@ func testLogFileEndpointLiveCluster(test Test, g *WithT, namespace *corev1.Names
212198 setClusterContext (test , g , client , historyServerURL , namespace .Name , rayCluster .Name , clusterInfo .SessionName )
213199
214200 nodeID := GetOneOfNodeID (g , client , historyServerURL )
215- // Hardcode "raylet.out" for deterministic testing.
216- filename := "raylet.out"
217201
218202 test .T ().Run ("should return log content" , func (t * testing.T ) {
219- g := NewWithT (t )
220- g .Eventually (func (gg Gomega ) {
221- logFileURL := fmt .Sprintf ("%s%s?node_id=%s&filename=%s&lines=100" , historyServerURL , EndpointLogFile , nodeID , filename )
222- resp , err := client .Get (logFileURL )
223- gg .Expect (err ).NotTo (HaveOccurred ())
224- defer resp .Body .Close ()
225- gg .Expect (resp .StatusCode ).To (Equal (http .StatusOK ))
226-
227- body , err := io .ReadAll (resp .Body )
228- gg .Expect (err ).NotTo (HaveOccurred ())
229- gg .Expect (len (body )).To (BeNumerically (">" , 0 ))
230- }, TestTimeoutShort ).Should (Succeed ())
203+ VerifyLogFileEndpointReturnsContent (test , NewWithT (t ), client , historyServerURL , nodeID )
231204 })
232205
233206 test .T ().Run ("should reject path traversal" , func (t * testing.T ) {
234- g := NewWithT (t )
235- maliciousPaths := []string {"../etc/passwd" , ".." , "/etc/passwd" , "../../secret" }
236-
237- for _ , malicious := range maliciousPaths {
238- g .Eventually (func (gg Gomega ) {
239- url := fmt .Sprintf ("%s%s?node_id=%s&filename=%s" , historyServerURL , EndpointLogFile , nodeID , malicious )
240- resp , err := client .Get (url )
241- gg .Expect (err ).NotTo (HaveOccurred ())
242- defer func () {
243- io .Copy (io .Discard , resp .Body )
244- resp .Body .Close ()
245- }()
246- gg .Expect (resp .StatusCode ).To (Equal (http .StatusBadRequest ))
247- }, TestTimeoutShort ).Should (Succeed ())
248- }
207+ VerifyLogFileEndpointRejectsPathTraversal (test , NewWithT (t ), client , historyServerURL , nodeID )
249208 })
250209
251210 DeleteS3Bucket (test , g , s3Client )
252211 LogWithTimestamp (test .T (), "Log file endpoint tests completed" )
253212}
254213
255214// testLogFileEndpointDeadCluster verifies that the history server can fetch log files from S3 after a cluster is deleted.
256- //
257- // The test case follows these steps:
258- // 1. Prepare test environment by applying a Ray cluster
259- // 2. Submit a Ray job to the existing cluster
260- // 3. Delete RayCluster to trigger log upload to S3
261- // 4. Apply History Server and get its URL
262- // 5. Verify that the history server can fetch log content from S3 (raylet.out)
263- // 6. Verify that the history server rejects path traversal attempts from S3
264- // 7. Delete S3 bucket to ensure test isolation
265215func testLogFileEndpointDeadCluster (test Test , g * WithT , namespace * corev1.Namespace , s3Client * s3.S3 ) {
266216 rayCluster := PrepareTestEnv (test , g , namespace , s3Client )
267217 ApplyRayJobAndWaitForCompletion (test , g , namespace , rayCluster )
268218
269- // Delete RayCluster to trigger log upload
270- err := test .Client ().Ray ().RayV1 ().RayClusters (namespace .Name ).Delete (test .Ctx (), rayCluster .Name , metav1.DeleteOptions {})
271- g .Expect (err ).NotTo (HaveOccurred ())
272- LogWithTimestamp (test .T (), "Deleted RayCluster %s/%s" , namespace .Name , rayCluster .Name )
273-
274- // Wait for cluster to be fully deleted (ensures logs are uploaded to S3)
275- g .Eventually (func () error {
276- _ , err := GetRayCluster (test , namespace .Name , rayCluster .Name )
277- return err
278- }, TestTimeoutMedium ).Should (WithTransform (k8serrors .IsNotFound , BeTrue ()))
219+ DeleteRayClusterAndWait (test , g , namespace .Name , rayCluster .Name )
279220
280221 ApplyHistoryServer (test , g , namespace , "" )
281222 historyServerURL := GetHistoryServerURL (test , g , namespace )
@@ -287,40 +228,13 @@ func testLogFileEndpointDeadCluster(test Test, g *WithT, namespace *corev1.Names
287228 setClusterContext (test , g , client , historyServerURL , namespace .Name , rayCluster .Name , clusterInfo .SessionName )
288229
289230 nodeID := GetOneOfNodeID (g , client , historyServerURL )
290- // Hardcode "raylet.out" for deterministic testing.
291- filename := "raylet.out"
292231
293232 test .T ().Run ("should return log content from S3" , func (t * testing.T ) {
294- g := NewWithT (t )
295- g .Eventually (func (gg Gomega ) {
296- logFileURL := fmt .Sprintf ("%s%s?node_id=%s&filename=%s&lines=100" , historyServerURL , EndpointLogFile , nodeID , filename )
297- resp , err := client .Get (logFileURL )
298- gg .Expect (err ).NotTo (HaveOccurred ())
299- defer resp .Body .Close ()
300- gg .Expect (resp .StatusCode ).To (Equal (http .StatusOK ))
301-
302- body , err := io .ReadAll (resp .Body )
303- gg .Expect (err ).NotTo (HaveOccurred ())
304- gg .Expect (len (body )).To (BeNumerically (">" , 0 ))
305- }, TestTimeoutShort ).Should (Succeed ())
233+ VerifyLogFileEndpointReturnsContent (test , NewWithT (t ), client , historyServerURL , nodeID )
306234 })
307235
308236 test .T ().Run ("should reject path traversal from S3" , func (t * testing.T ) {
309- g := NewWithT (t )
310- maliciousPaths := []string {"../etc/passwd" , ".." , "/etc/passwd" , "../../secret" }
311-
312- for _ , malicious := range maliciousPaths {
313- g .Eventually (func (gg Gomega ) {
314- url := fmt .Sprintf ("%s%s?node_id=%s&filename=%s" , historyServerURL , EndpointLogFile , nodeID , malicious )
315- resp , err := client .Get (url )
316- gg .Expect (err ).NotTo (HaveOccurred ())
317- defer func () {
318- io .Copy (io .Discard , resp .Body )
319- resp .Body .Close ()
320- }()
321- gg .Expect (resp .StatusCode ).To (Equal (http .StatusBadRequest ))
322- }, TestTimeoutShort ).Should (Succeed ())
323- }
237+ VerifyLogFileEndpointRejectsPathTraversal (test , NewWithT (t ), client , historyServerURL , nodeID )
324238 })
325239
326240 DeleteS3Bucket (test , g , s3Client )
0 commit comments