Skip to content

Commit a9583e3

Browse files
committed
Remove the transitional Job.compute_profile string field (step 1)
Job.compute_profile (string) was a stopgap superseded by compute_profile_fk (FK -> ComputeProfile), the source of truth for the profile a job ran on. The two were written together and always agreed at creation; this retires the string. Step 1 of a two-step removal (mirrors Program.default_compute_profile, #2420 -> #2421): repoint every reader to the FK via a new null-safe Job.compute_profile_id property, then remove the field from Django's model STATE only. The DB column is left in place so the previous release stays deployable; the real DROP COLUMN ships in a later PR once no field-declaring release can deploy. - Readers repointed to compute_profile_id: fleet name + resource limits (fleets_runner), billing classical metric type (kafka_event_streams_client), admin job timeline (+ select_related to avoid an N+1). - Writers: run path and filler-jobs path no longer write the string (both already set compute_profile_fk). - API: compute_profile dropped from job serializers; compute_profile_fk (already present) carries the profile. Client Job loses its compute_profile attribute. - Admin: field removed from the JobAdmin Fleets fieldset. - Migration 0064: state-only RemoveField wrapped in SeparateDatabaseAndState. Out of scope: the DROP COLUMN (step 2) and the deprecated compute_profile run input kwarg (use function_size).
1 parent d311370 commit a9583e3

22 files changed

Lines changed: 125 additions & 101 deletions

File tree

client/qiskit_serverless/core/clients/serverless_client.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,6 @@ def jobs(self, function: Optional[QiskitFunction] = None, **kwargs) -> List[Job]
236236
job.get("id"),
237237
job_service=self,
238238
raw_data=job,
239-
compute_profile=job.get("compute_profile"),
240239
)
241240
for job in response_data.get("results", [])
242241
]
@@ -291,7 +290,6 @@ def provider_jobs(self, function: Optional[QiskitFunction], **kwargs) -> List[Jo
291290
job.get("id"),
292291
job_service=self,
293292
raw_data=job,
294-
compute_profile=job.get("compute_profile"),
295293
)
296294
for job in response_data.get("results", [])
297295
]
@@ -314,7 +312,6 @@ def job(self, job_id: str) -> Optional[Job]:
314312
job = Job(
315313
job_id=job_id,
316314
job_service=self,
317-
compute_profile=response_data.get("compute_profile"),
318315
)
319316

320317
return job
@@ -376,7 +373,6 @@ def run(
376373
return Job(
377374
job_id,
378375
job_service=self,
379-
compute_profile=response_data.get("compute_profile"),
380376
)
381377

382378
def get_job_data(self, job_id: str) -> Optional[dict]:

client/qiskit_serverless/core/job.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ def __init__(
168168
job_id: str,
169169
job_service: JobService,
170170
raw_data: Optional[Dict[str, Any]] = None,
171-
compute_profile: Optional[str] = None,
172171
):
173172
"""Job class for async script execution.
174173
@@ -178,7 +177,6 @@ def __init__(
178177
"""
179178
self.job_id = job_id
180179
self._job_service = job_service
181-
self.compute_profile = compute_profile
182180
self.raw_data = raw_data or {}
183181

184182
@property

client/tests/core/test_serverless_client_jobs.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,16 +1140,13 @@ def test_run_with_compute_profile(self, mock_client):
11401140
json=mock_response,
11411141
)
11421142

1143-
job = mock_client.run(program="test-program", compute_profile="gx3d-24x120x1a100p")
1143+
mock_client.run(program="test-program", compute_profile="gx3d-24x120x1a100p")
11441144

1145-
# Verify client sent compute_profile to API
1145+
# Verify client sent the (deprecated) compute_profile input to the API
11461146
request_data = json.loads(mock_request.last_request.text)
11471147
assert "compute_profile" in request_data
11481148
assert request_data["compute_profile"] == "gx3d-24x120x1a100p"
11491149

1150-
# Verify Job.compute_profile property works
1151-
assert job.compute_profile == "gx3d-24x120x1a100p"
1152-
11531150
def test_run_without_compute_profile(self, mock_client):
11541151
"""Test run() without compute_profile - backend applies default."""
11551152
# Mock response includes default compute_profile applied by backend
@@ -1169,11 +1166,8 @@ def test_run_without_compute_profile(self, mock_client):
11691166
json=mock_response,
11701167
)
11711168

1172-
job = mock_client.run(program="test-program")
1169+
mock_client.run(program="test-program")
11731170

11741171
# Verify client sent compute_profile as None (backend will apply default)
11751172
request_data = json.loads(mock_request.last_request.text)
11761173
assert request_data["compute_profile"] is None
1177-
1178-
# Verify backend applied the default
1179-
assert job.compute_profile == "cx3d-4x16"

gateway/api/admin.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,6 @@ class JobAdmin(admin.ModelAdmin):
505505
"fields": [
506506
"filler",
507507
"fleet_id",
508-
"compute_profile",
509508
"compute_profile_fk",
510509
"size_source",
511510
"function_size",
@@ -575,7 +574,11 @@ def job_timeline_view(self, request):
575574
continue
576575
# one single query: the rendering iterates the jobs again, and re-running the queryset
577576
# could come back empty (deleted in between) and break the rendering half way through
578-
jobs = list(Job.objects.filter(id__in=id_list).select_related("author").prefetch_related("job_events"))
577+
jobs = list(
578+
Job.objects.filter(id__in=id_list)
579+
.select_related("author", "compute_profile_fk")
580+
.prefetch_related("job_events")
581+
)
579582
if not id_list or not jobs:
580583
messages.error(request, "No jobs selected for the timeline.")
581584
return redirect(reverse("admin:api_job_changelist"))
@@ -725,7 +728,7 @@ def index(self, request, extra_context=None):
725728
if timeline_context is None:
726729
recent_jobs = list(
727730
Job.objects.filter(runner=Program.FLEETS)
728-
.select_related("author")
731+
.select_related("author", "compute_profile_fk")
729732
.prefetch_related("job_events")
730733
.order_by("-created")[:20]
731734
)

gateway/api/domain/job_timeline.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def _jobs_from_queryset(jobs_qs):
6767
"status": job.status,
6868
"runner": job.runner,
6969
"filler": job.filler,
70-
"profile": job.compute_profile or "-",
70+
"profile": job.compute_profile_id or "-",
7171
"created": job.created,
7272
"updated": job.updated,
7373
"running_started_at": job.running_started_at,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from django.db import migrations
2+
3+
4+
class Migration(migrations.Migration):
5+
6+
dependencies = [
7+
("api", "0063_merge_20260902_1720"),
8+
]
9+
10+
operations = [
11+
# Step 1 of a two-step removal of Job.compute_profile (the transitional
12+
# string). compute_profile_fk (FK -> ComputeProfile) is the source of
13+
# truth and every reader now goes through Job.compute_profile_id, which
14+
# reads the FK. Here the field is dropped from Django's state ONLY; the
15+
# api_job.compute_profile column is left in place so the previous release
16+
# -- whose Job model still declares the field and lists it in every SELECT
17+
# -- stays deployable. The real DROP COLUMN happens in a later release,
18+
# once no release that declares the field can still be deployed (mirrors
19+
# the Program.default_compute_profile removal, migrations 0057 -> 0058).
20+
migrations.SeparateDatabaseAndState(
21+
state_operations=[
22+
migrations.RemoveField(
23+
model_name="job",
24+
name="compute_profile",
25+
),
26+
],
27+
database_operations=[],
28+
),
29+
]

gateway/api/use_cases/programs/run.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,11 @@ def _get_runner_config(
7272
) -> RunnerConfig:
7373
"""Resolve the compute profile and sizing provenance for a run.
7474
75-
``compute_profile`` (string) is transitional and will be removed; the FK
76-
``compute_profile_fk`` is the source of truth going forward. They always agree
77-
at creation. A Fleets job always resolves to a profile string; if no
78-
``ComputeProfile`` row is registered for it, that is a deployment
79-
misconfiguration and we reject the job rather than store a null FK. Ray leaves
80-
``compute_profile`` None (profiles are a Fleets concept), so the FK stays null.
75+
``compute_profile_fk`` is the source of truth for the profile a job runs at
76+
and is what gets stored on the job. A Fleets job always resolves to a profile;
77+
if no ``ComputeProfile`` row is registered for it, that is a deployment
78+
misconfiguration and we reject the job rather than store a null FK. Ray is not
79+
profiled (profiles are a Fleets concept), so its FK stays null.
8180
8281
Because the size determines the compute profile (and not the reverse -- two
8382
sizes can map to one profile), the returned :class:`RunnerConfig` also records
@@ -237,7 +236,6 @@ def execute( # pylint: disable=too-many-locals, too-many-branches
237236
author=user,
238237
gpu=runner_config.gpu,
239238
runner=function.runner,
240-
compute_profile=runner_config.compute_profile,
241239
compute_profile_fk=runner_config.compute_profile_fk,
242240
size_source=runner_config.size_source,
243241
function_size=runner_config.function_size,

gateway/api/v1/views/jobs/list.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class JobSerializerWithoutResult(serializers.ModelSerializer):
8686

8787
class Meta:
8888
model = Job
89-
fields = ["id", "status", "program", "created", "sub_status", "compute_profile", "compute_profile_fk"]
89+
fields = ["id", "status", "program", "created", "sub_status", "compute_profile_fk"]
9090
ref_name = "JobsListWithoutResultInputSerializer"
9191

9292

gateway/api/v1/views/jobs/retrieve.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ class Meta:
8282
"created",
8383
"sub_status",
8484
"fleet_id",
85-
"compute_profile",
8685
"compute_profile_fk",
8786
"business_model",
8887
]
@@ -113,7 +112,6 @@ class Meta:
113112
"created",
114113
"sub_status",
115114
"fleet_id",
116-
"compute_profile",
117115
"compute_profile_fk",
118116
"business_model",
119117
]

gateway/api/v1/views/programs/get_jobs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class OutputSerializer(serializers.ModelSerializer):
5050

5151
class Meta:
5252
model = Job
53-
fields = ["id", "result", "status", "program", "created", "sub_status", "fleet_id", "compute_profile"]
53+
fields = ["id", "result", "status", "program", "created", "sub_status", "fleet_id"]
5454
ref_name = "ProgramsGetJobsOutput"
5555

5656

@@ -65,7 +65,7 @@ class OutputSerializerWithoutResult(serializers.ModelSerializer):
6565

6666
class Meta:
6767
model = Job
68-
fields = ["id", "status", "program", "created", "sub_status", "fleet_id", "compute_profile"]
68+
fields = ["id", "status", "program", "created", "sub_status", "fleet_id"]
6969
ref_name = "ProgramsGetJobsOutputWithoutResult"
7070

7171

0 commit comments

Comments
 (0)