Skip to content

Commit cb86f9f

Browse files
jugalshah291Jugal Shah
andauthored
Get details of only declarative serve apps (#4084)
* Get details of only declarative serve apps Signed-off-by: Jugal Shah <[email protected]> * Fix CI errors Signed-off-by: Jugal Shah <[email protected]> * Fix linting in dashboard_httpclient_test.go Signed-off-by: Jugal Shah <[email protected]> --------- Signed-off-by: Jugal Shah <[email protected]> Co-authored-by: Jugal Shah <[email protected]>
1 parent 759ab3a commit cb86f9f

File tree

2 files changed

+137
-2
lines changed

2 files changed

+137
-2
lines changed

ray-operator/controllers/ray/utils/dashboardclient/dashboard_httpclient.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io"
88
"net/http"
9+
"net/url"
910
"strings"
1011

1112
"k8s.io/apimachinery/pkg/api/errors"
@@ -19,6 +20,7 @@ import (
1920
var (
2021
// Multi-application URL paths
2122
ServeDetailsPath = "/api/serve/applications/"
23+
APITypeParam = "declarative"
2224
DeployPathV2 = "/api/serve/applications/"
2325
// Job URL paths
2426
JobPath = "/api/jobs/"
@@ -85,9 +87,17 @@ func (r *RayDashboardClient) GetMultiApplicationStatus(ctx context.Context) (map
8587
return r.ConvertServeDetailsToApplicationStatuses(serveDetails)
8688
}
8789

88-
// GetServeDetails gets details on all live applications on the Ray cluster.
90+
// GetServeDetails gets details on all declarative applications on the Ray cluster.
8991
func (r *RayDashboardClient) GetServeDetails(ctx context.Context) (*utiltypes.ServeDetails, error) {
90-
req, err := http.NewRequestWithContext(ctx, http.MethodGet, r.dashboardURL+ServeDetailsPath, nil)
92+
serveDetailsURL, err := url.Parse(r.dashboardURL + ServeDetailsPath)
93+
if err != nil {
94+
return nil, fmt.Errorf("failed to parse dashboard URL: %w", err)
95+
}
96+
q := serveDetailsURL.Query()
97+
q.Set("api_type", APITypeParam)
98+
serveDetailsURL.RawQuery = q.Encode()
99+
100+
req, err := http.NewRequestWithContext(ctx, "GET", serveDetailsURL.String(), nil)
91101
if err != nil {
92102
return nil, err
93103
}

ray-operator/controllers/ray/utils/dashboardclient/dashboard_httpclient_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,4 +172,129 @@ var _ = Describe("RayFrameworkGenerator", func() {
172172
err := rayDashboardClient.StopJob(context.TODO(), "stop-job-1")
173173
Expect(err).ToNot(HaveOccurred())
174174
})
175+
176+
It("Test GetServeDetails URL construction with query parameters", func() {
177+
httpmock.Activate()
178+
defer httpmock.DeactivateAndReset()
179+
180+
// Mock the response
181+
expectedResponse := &utiltypes.ServeDetails{
182+
Applications: map[string]utiltypes.ServeApplicationDetails{
183+
"app1": {
184+
ServeApplicationStatus: utiltypes.ServeApplicationStatus{
185+
Name: "app1",
186+
Status: "RUNNING",
187+
},
188+
Deployments: map[string]utiltypes.ServeDeploymentDetails{},
189+
},
190+
},
191+
DeployMode: "MULTI_APP",
192+
}
193+
responseBytes, _ := json.Marshal(expectedResponse)
194+
195+
// Register responder that checks the URL includes the query parameter
196+
httpmock.RegisterResponder(http.MethodGet, rayDashboardClient.dashboardURL+ServeDetailsPath,
197+
func(req *http.Request) (*http.Response, error) {
198+
// Verify the query parameter is present
199+
Expect(req.URL.Query().Get("api_type")).To(Equal("declarative"))
200+
return httpmock.NewBytesResponse(200, responseBytes), nil
201+
})
202+
203+
details, err := rayDashboardClient.GetServeDetails(context.TODO())
204+
Expect(err).ToNot(HaveOccurred())
205+
Expect(details.Applications).To(HaveKey("app1"))
206+
Expect(details.DeployMode).To(Equal("MULTI_APP"))
207+
})
208+
209+
It("Test GetServeDetails with invalid dashboard URL", func() {
210+
invalidClient := &RayDashboardClient{
211+
dashboardURL: "://invalid-url",
212+
client: &http.Client{},
213+
}
214+
215+
_, err := invalidClient.GetServeDetails(context.TODO())
216+
Expect(err).To(HaveOccurred())
217+
Expect(err.Error()).To(ContainSubstring("failed to parse dashboard URL"))
218+
})
219+
220+
It("Test GetServeDetails with HTTP 400 Bad Request", func() {
221+
httpmock.Activate()
222+
defer httpmock.DeactivateAndReset()
223+
224+
// Test 400 Bad Request (might happen with older Ray versions)
225+
httpmock.RegisterResponder(http.MethodGet, rayDashboardClient.dashboardURL+ServeDetailsPath,
226+
func(_ *http.Request) (*http.Response, error) {
227+
return httpmock.NewStringResponse(400, "Bad Request: unknown parameter api_type"), nil
228+
})
229+
230+
_, err := rayDashboardClient.GetServeDetails(context.TODO())
231+
Expect(err).To(HaveOccurred())
232+
Expect(err.Error()).To(ContainSubstring("GetServeDetails fail: 400"))
233+
Expect(err.Error()).To(ContainSubstring("Bad Request: unknown parameter api_type"))
234+
})
235+
236+
It("Test GetServeDetails with HTTP 500 Internal Server Error", func() {
237+
httpmock.Activate()
238+
defer httpmock.DeactivateAndReset()
239+
240+
httpmock.RegisterResponder(http.MethodGet, rayDashboardClient.dashboardURL+ServeDetailsPath,
241+
func(_ *http.Request) (*http.Response, error) {
242+
return httpmock.NewStringResponse(500, "Internal Server Error"), nil
243+
})
244+
245+
_, err := rayDashboardClient.GetServeDetails(context.TODO())
246+
Expect(err).To(HaveOccurred())
247+
Expect(err.Error()).To(ContainSubstring("GetServeDetails fail: 500"))
248+
})
249+
250+
It("Test GetServeDetails with invalid JSON response", func() {
251+
httpmock.Activate()
252+
defer httpmock.DeactivateAndReset()
253+
254+
httpmock.RegisterResponder(http.MethodGet, rayDashboardClient.dashboardURL+ServeDetailsPath,
255+
func(_ *http.Request) (*http.Response, error) {
256+
return httpmock.NewStringResponse(200, "invalid json response"), nil
257+
})
258+
259+
_, err := rayDashboardClient.GetServeDetails(context.TODO())
260+
Expect(err).To(HaveOccurred())
261+
Expect(err.Error()).To(ContainSubstring("GetServeDetails failed. Failed to unmarshal bytes"))
262+
Expect(err.Error()).To(ContainSubstring("invalid json response"))
263+
})
264+
265+
It("Test GetServeDetails with empty response", func() {
266+
httpmock.Activate()
267+
defer httpmock.DeactivateAndReset()
268+
269+
// Test with empty but valid JSON
270+
emptyResponse := &utiltypes.ServeDetails{
271+
Applications: map[string]utiltypes.ServeApplicationDetails{},
272+
DeployMode: "",
273+
}
274+
responseBytes, _ := json.Marshal(emptyResponse)
275+
276+
httpmock.RegisterResponder(http.MethodGet, rayDashboardClient.dashboardURL+ServeDetailsPath,
277+
func(req *http.Request) (*http.Response, error) {
278+
// Verify the query parameter is present
279+
Expect(req.URL.Query().Get("api_type")).To(Equal("declarative"))
280+
return httpmock.NewBytesResponse(200, responseBytes), nil
281+
})
282+
283+
details, err := rayDashboardClient.GetServeDetails(context.TODO())
284+
Expect(err).ToNot(HaveOccurred())
285+
Expect(details.Applications).To(BeEmpty())
286+
Expect(details.DeployMode).To(Equal(""))
287+
})
288+
289+
It("Test GetServeDetails with network error", func() {
290+
httpmock.Activate()
291+
defer httpmock.DeactivateAndReset()
292+
293+
httpmock.RegisterResponder(http.MethodGet, rayDashboardClient.dashboardURL+ServeDetailsPath,
294+
httpmock.NewErrorResponder(context.DeadlineExceeded))
295+
296+
_, err := rayDashboardClient.GetServeDetails(context.TODO())
297+
Expect(err).To(HaveOccurred())
298+
Expect(err).To(Equal(context.DeadlineExceeded))
299+
})
175300
})

0 commit comments

Comments
 (0)