Skip to content

Fleets results: return presigned URL redirect instead of buffering content#2203

Open
avilches wants to merge 36 commits into
mainfrom
worktree-fleet-results-presigned-url
Open

Fleets results: return presigned URL redirect instead of buffering content#2203
avilches wants to merge 36 commits into
mainfrom
worktree-fleet-results-presigned-url

Conversation

@avilches

@avilches avilches commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Continuing the approach from #2186 with the Fleet logs. This PR adds GET /jobs/{id}/result/ as a new endpoint for downloiad job results and updates the Python client to use it.

New endpoint: GET /jobs/{id}/result/

Serves both Ray and Fleet jobs, but behaves differently by runner:

  • Fleet: checks whether results.json exists in COS and returns 302 redirect to a presigned URL (result ready) or 204 No Content (not ready yet). The gateway never download the COS content.
  • Ray: a redirect is not possible because Ray results live on the gateway's COS volume mounted, so the endpoint reads the file and returns the content inline as a 200 JSON response.

The existing GET /jobs/{id}/ endpoint is unchanged and still supports ?with_result=true for backward compatibility with older clients.

Client: result()

  • Before: called GET /jobs/{id}/?with_result=true.
  • After: calls GET /jobs/{id}/result/ and handles 302 (follows redirect to COS, decodes JSON from the COS response), 204 (returns None), and 200 (decodes response["result"] as before for Ray jobs).

Client: job()

  • Before: called GET /jobs/{id}/ without an explicit with_result parameter. The server defaulted to with_result=true, so it read the result from COS or the local filesystem on every call.
  • After: explicitly passes with_result=false. The server skips the storage read entirely and returns result: null. This does not break anything: job() only uses id and compute_profile from the response and never exposed the result field to the caller. The result has always been fetched separately via result().

@avilches avilches requested a review from a team as a code owner June 8, 2026 12:17
@avilches avilches self-assigned this Jun 8, 2026
@avilches avilches added the project: fleets Label to identify fleets features label Jun 8, 2026
@avilches avilches changed the title Add presigned URL redirect for Fleet job results Fleets results: return presigned URL redirect for Fleet job results Jun 8, 2026
@avilches avilches changed the title Fleets results: return presigned URL redirect for Fleet job results Fleets results: return presigned URL redirect instead of buffering content Jun 8, 2026
@avilches avilches marked this pull request as draft June 8, 2026 12:40
@avilches

avilches commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

The PR shares code from #2186, so it needs to be merged after it (and probably solve conflicts)

@avilches avilches force-pushed the worktree-fleet-results-presigned-url branch from 0961a34 to 928da2c Compare June 8, 2026 14:39
request=lambda: requests.get(
url,
headers=get_headers(token=self.token, instance=self.instance, channel=self.channel),
params={"with_result": "false"},

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default for with_result is true, so now has been changed explicitly to false, so the Gateway skips the COS read and returns result: null. This does not break anything because job() don't use the result.

Comment on lines +416 to +429
gateway_url = f"{self.host}/api/{self.version}/jobs/{job_id}/result/"
response = requests.get(
gateway_url,
headers=get_headers(token=self.token, instance=self.instance, channel=self.channel),
timeout=REQUESTS_TIMEOUT,
)
return json.loads(response_data.get("result", "{}") or "{}", cls=QiskitObjectsDecoder)
if response.status_code == 204:
return None
# Not all redirects go to COS — HTTP→HTTPS redirects stay on the same host.
# Checking the hostname detects only redirects to an external host (COS/MinIO).
redirected_to_cos = urlparse(response.url).hostname != urlparse(gateway_url).hostname
if redirected_to_cos:
return json.loads(response.text, cls=QiskitObjectsDecoder) if response.ok else None
return json.loads(response.json().get("result", "{}") or "{}", cls=QiskitObjectsDecoder)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new /result endpoint following the same approach than /logs and /provider-logs


@dataclass
class LogsResult:
class GetLogsResponse:

@avilches avilches Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LogsResult was a terrible name. Now, GetJobLogsUseCase -> returns GetJobLogsResponse and GetResultUseCase -> returns GetResultResponse

Comment thread gateway/core/services/storage/result_storage_fleets.py
Comment thread client/qiskit_serverless/core/clients/serverless_client.py Outdated
Comment thread gateway/api/use_cases/jobs/get_result_response.py Outdated
Comment thread gateway/core/services/storage/result_storage_fleets.py Outdated
@avilches avilches marked this pull request as ready for review June 9, 2026 14:59
Comment thread gateway/api/v1/views/jobs/save_result.py Outdated
Comment thread gateway/api/v1/views/jobs/save_result.py Outdated


@swagger_auto_schema(
method="post",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a swagger auto schema for GET

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in this commit

if result is not None:
job.result = result
# with_result is legacy, new clients will use the new GET /:id/results endpoints
if with_result:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to ensure then that this with_result is false. I think we need to change it in the serializer if I remember correctly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed: the default value was true. I changed to false in this commit

Comment thread gateway/tests/core/services/storage/test_result_storage.py Outdated
@avilches avilches requested a review from Tansito June 9, 2026 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

project: fleets Label to identify fleets features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants