test(integration): slice spawn + restart regression guards (#2632) - #2651
Conversation
New `integration_tests/regression/` directory pins the slice-DAG invariants from #2632: - `test_slice_spawn_env_threading.py` (green): each per-slice agent spawn lands `EGG_BRANCH=egg/<pid>/slice-<N>` and `EGG_SLICE_ID=slice-<N>` on the pod spec even when an upstream `extra_env` ships a conflicting pipeline-level `EGG_BRANCH`. Pins #2428 + #2410 + #2403. - `test_slice_restart_branch_invariants.py` (xfail, strict): two tests pinning that `restart_agent_job` preserves `EGG_BRANCH` / `EGG_SLICE_ID` across a slice restart and that pipeline-level restarts don't disturb slice-scoped Jobs. Currently blocked on #2644 (surfaced while writing these tests). `README.md` enumerates the gap audit for what should be in this directory but isn't yet — most deferred items wait on the ScriptedProvider pod-injection infra (#2585) called out in the #2474 constraint.
Self-audit fallout from #2632 review feedback: - New `test_baseline_spawn_without_extra_env_override`: exercises the default `EGG_BRANCH` derivation path (no conflicting `extra_env`) so a regression in the default flow isn't masked by the override-rejection path. - New `test_long_name_round_trip.py`: focused k3s regression guard for #2644 — creates a Job with an input name long enough to trigger truncation, then proves `delete_job` round-trips it. - Split the Foreground-deletion/respawn race out of #2644 into its own issue (#2655). Even after #2644's truncation fix the race persists, so the restart tests need both fixed before they un-xfail. - README + restart-test xfail reasons updated to reference both.
Bundle the production fixes that PR #2651's regression tests pinned: - #2644: extract `KubernetesClient._normalize_k8s_job_name` from `create_container` and apply it in `delete_job` / `get_pod_for_job`. Long Job names (>63 chars) now round-trip symmetrically: the prefix + truncation + SHA-1 digest that `create_container` stamps on creation is also what those methods see on the way back. Without this, the spawner's `restart_agent_job` issued a silent-404 delete against the un-truncated name while the Job actually existed under the truncated form. - #2655: add `KubernetesClient.wait_for_job_gone` and call it from `KubernetesSpawner.restart_agent_job` between the Foreground delete and the respawn. Foreground propagation returns as soon as the deletion is accepted; the Job lingers with its finalizer until pods are gone. Without the wait, the immediate respawn raced the finalizer and 409'd on AlreadyExists. - #2666: add `LABEL_SLICE_ID` ("egg.slice.id") and apply it conditionally on slice-scoped spawns. Adds a `KubernetesSpawner.list_slice_jobs(pipeline_id, slice_id)` helper so callers don't have to parse Job names to scope per slice. Flip the three `xfail(strict=True)` regression tests in this PR (`test_long_name_round_trip.py` + the two restart tests in `test_slice_restart_branch_invariants.py`) back to non-xfail. Unit-tier coverage: 4 new tests in `test_kubernetes_client.py` covering long-name normalization in `delete_job`, the create+delete round-trip, and the four `wait_for_job_gone` states. Closes #2644 Closes #2655 Closes #2666
This comment has been minimized.
This comment has been minimized.
When the gateway's Squid proxy is down it reports status "degraded" rather than "healthy". The spawner's wait_for_gateway check was treating degraded as a hard failure regardless of spawn mode. For public-mode spawns Squid is not used (containers connect directly), so a degraded gateway is still fully functional. Only private-mode spawns require Squid to be listening. This fixes the two failing integration tests in test_slice_spawn_env_threading.py which call spawn_agent_job(mode="public") and fail with "Gateway is not healthy: degraded" in CI where Squid may not be up yet when tests run. Also adds two unit tests to pin the new behavior.
Autofix tracking{"Test/Integration Tests / Integration Tests": 2} |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Review
Thorough pass over the diff plus surrounding context in orchestrator/kubernetes_client.py, orchestrator/kubernetes_spawner.py, orchestrator/gateway_client.py, gateway/gateway.py, and the new integration_tests/regression/ tree.
The core production fixes (#2644 delete_job name normalization, #2655 wait_for_job_gone between delete + respawn, #2666 egg.slice.id label + list_slice_jobs) are correct and the regression tests are well-shaped (real spawner, real k3s, real pod-spec inspection — no hand-built fixtures bypassing production helpers). However, two issues block merge: a stowaway production behavior change with an incorrect comment, and a README that contradicts its own test files.
Blocking
1. orchestrator/kubernetes_spawner.py:487-498 — "degraded" gateway bypass is wrong about what degraded means.
if health.status == "degraded" and mode == "public":
logger.debug(
"Gateway degraded (Squid down) but mode is public; proceeding",
status=health.status,
)The comment and log message both claim degraded ⇒ Squid down. That is not what the gateway emits. From gateway/gateway.py:949:
is_healthy = token_valid and launcher_secret_configured and squid_status["listening"]
...
"status": "healthy" if is_healthy else "degraded",degraded is the logical OR of three failure modes: invalid GitHub token, missing launcher secret, or Squid down. GatewayHealth (orchestrator/gateway_client.py:126-133) only carries the top-level status string — the squid_proxy / github_token_valid / auth_configured fields the gateway exposes are discarded by check_health(). So the spawner cannot distinguish "Squid down" from "GitHub token invalid" at this seam.
The net effect: a production deployment with a revoked or rotated GitHub token will report degraded, and public-mode spawns will now proceed silently — agents will spin up, then fail later at the first gateway-mediated git/gh operation with a confusing downstream error instead of the clear "Gateway is not healthy" we used to surface at spawn time. This is a real regression in operator signal.
Three options, pick one:
- Don't change production at all. The regression tests can pass
wait_for_gateway=Falsetospawn_agent_job— that parameter already exists (kubernetes_spawner.py:420) and is the right knob for "I know the gateway isn't fully healthy in this environment." This is the cleanest fix since the change is in service of integration test setup, not a production concern. - Tighten the predicate to actually mean Squid-only. Extend
GatewayHealthto surfacesquid_proxy/github_token_valid/auth_configuredfrom the JSON (they're already in the response), then gate the bypass onhealth.squid_proxy["listening"] is False and health.github_token_valid and health.auth_configured. That makes the comment true. - Drop the change. If neither of the above is desired, the bypass shouldn't ship.
This is also scope creep relative to the PR description (which calls out #2644 / #2655 / #2666 explicitly but never mentions a gateway-health policy change). It should either be its own PR with its own justification, or removed.
The new unit tests test_spawn_degraded_gateway_public_mode_proceeds / test_spawn_degraded_gateway_private_mode_raises only test the broad behavior — they don't (can't) distinguish the cause of degradation, so they confirm the buggy assumption rather than catching it.
2. integration_tests/regression/README.md status column contradicts the test files in this same PR.
The README's "What's covered today" table marks all three tests as xfail(strict=True):
test_long_name_round_trip.py| ... |⚠️ xfail(strict=True)— blocked on #2644
test_slice_restart_branch_invariants.py::test_restart_preserves_egg_branch_and_slice_id| ... |⚠️ xfail(strict=True)— blocked on #2644 + #2655
test_slice_restart_branch_invariants.py::test_restart_isolates_slice_from_pipeline_level_agent| ... |⚠️ xfail(strict=True)— blocked on #2644 + #2655
But the PR description says those tests are "now green — was xfail" (because the production fixes ship in this same PR), and grep -n "xfail" integration_tests/regression/*.py returns no markers. All three tests are unconditionally pytestmark = pytest.mark.integration with no xfail. The README needs to be updated to "✅ green" rows; otherwise the next reader looking at this directory sees a documented invariant ("xfail discipline: xfail(strict=True, reason=...) only — never skip") that no test in the directory actually demonstrates.
Non-blocking
3. kubernetes_client.py:756-761 — wait_for_job_gone swallows non-404 exceptions silently.
except Exception as exc:
msg = str(exc).lower()
if "not found" in msg or "404" in msg:
return TrueA 500, an RBAC denial, or a transient ConnectionError is caught, the message check fails, and the loop continues until timeout — at which point the function returns False and the caller (restart_agent_job) logs "Job still present after 30s wait". That message is wrong when the real failure is "API server is unreachable" or "the SA lost permission to read jobs." Consider matching on kubernetes.client.exceptions.ApiException.status == 404 rather than substring on the str, and logging (or re-raising) anything else. Substring matching on "not found" in msg is also false-positive prone — any nested error mentioning that phrase will be misread as a 404.
4. test_returns_true_after_job_disappears does a real time.sleep(0.5).
orchestrator/tests/test_kubernetes_client.py:1380-1389 relies on the helper's poll_interval = 0.5 to actually elapse. Each invocation of this test costs ~0.5s. Patch time.sleep so unit tests stay sub-millisecond. Same applies to test_returns_false_on_timeout, which sleeps once before timing out.
5. integration_tests/regression/conftest.py:236-258 — EGG_LAUNCHER_SECRET env-var write is dead code.
prev_secret = os.environ.get("EGG_LAUNCHER_SECRET")
os.environ["EGG_LAUNCHER_SECRET"] = egg_stack.launcher_secret
try:
gateway = GatewayClient(
gateway_host=host,
gateway_port=port,
launcher_secret=egg_stack.launcher_secret, # ← already explicit
)launcher_secret is passed explicitly to GatewayClient, so the env-var fallback never fires. The mutation + restore dance is just risk surface (a parallel fixture in the same process briefly sees a different value). Drop the env-var manipulation.
6. test_each_slice_gets_its_own_branch_env is named "concurrent" but is sequential.
The README ("three concurrent slice agents") and the test docstring ("Three concurrent slices") promise concurrency that the implementation doesn't deliver — the body is a plain for slice_id in slices: with serial spawns. The test still catches the #2428 regression (each spawn is independently correct), but a regression that only manifests under genuine concurrency (e.g. a future change to spawn_agent_job that takes a shared lock and clobbers in-flight state between siblings) would slip past. Either thread the spawns (the canonical k3s-side concurrency repro shape) or update the wording to "three back-to-back slices" so the README isn't lying about coverage.
7. test_long_name_create_then_delete_round_trips has no autouse cleanup fixture.
It does best-effort cleanup in a finally: block, which is fine for the happy path, but if pytest is interrupted between create_container and the finally, the long-named Job persists in the test namespace. The other regression tests use a cleanup_jobs autouse-style fixture. Consider adopting the same pattern here for symmetry — the README's "Conventions" section calls out exactly this ("every test paired with a cleanup_jobs autouse fixture").
8. KubernetesClient._normalize_k8s_job_name has no direct unit test asserting its output.
The new tests test_delete_job_normalizes_long_name / test_normalizes_long_name assert that delete_job and wait_for_job_gone call the API with _normalize_k8s_job_name(input) — i.e., they're tautological with respect to the helper. The integration test_long_name_create_then_delete_round_trips is the only thing that exercises the actual truncation math, and only indirectly. A direct unit test (assert _normalize_k8s_job_name("egg-sandbox-" + "x"*58) == "egg-sandbox-xxxxxxxx...<digest>" for a known fixed input/output) would catch a regression in the helper itself — e.g. if someone tweaked the [:54] slice to [:53] and broke uniqueness.
9. Comment at kubernetes_client.py:756 calls the exception variable exc but never uses it.
Trivial; as exc can be dropped, or it should be logged for debuggability when it's not a 404. Lean toward logging — it's free signal for the "what broke" debugging path.
Things that look right
_normalize_k8s_job_nameis idempotent — applying it to an already-prefixed-and-truncated name is a no-op (startswith(JOB_PREFIX)⇒ skip prefix add,len ≤ 63⇒ skip truncate). Bothdelete_job(long input from_build_k8s_job_names) and the internal callers via_resolve_job_name(already-prefixed) round-trip correctly.- The
delete_attemptedflag inrestart_agent_jobcorrectly staysFalseonPodNotFoundError— no point waiting for a Job that never existed — but flipsTrueonly on actual delete success. TheJobOperationErrorbranch also staysFalse, which is the safer default (don't wait when we don't know the delete went through). _PROTECTED_ENV_KEYSincludesEGG_BRANCHandEGG_SLICE_ID(kubernetes_spawner.py:106, 118), sotest_each_slice_gets_its_own_branch_env'sextra_env={"EGG_BRANCH": ...}override correctly gets dropped — the test does exercise the real seam, not a bypass.LABEL_SLICE_ID = "egg.slice.id"is gated onslice_id is not None(kubernetes_spawner.py:515-516), so pipeline-level Jobs don't accumulate an empty/null slice label —list_slice_jobsmatches only the intended Jobs.get_pod_for_job's new selector behavior (job-name=egg-sandbox-my-jobinstead of the priorjob-name=my-jobwhen called with an un-prefixed name) is a strict bug fix: the Job controller stamps the truncated, prefixed form on dependent pods, so the prior selector for an un-prefixed input matched nothing. All internal callers go through_resolve_job_namewhich already returns the prefixed form, so they're unaffected.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Revert the production gateway-degraded bypass in KubernetesSpawner.spawn_agent_job — it was added to make the integration tests run against a degraded gateway but silently bypassed health gates for non-Squid failure modes (token revoked, launcher secret missing). The integration tests now pass wait_for_gateway=False directly; this parameter already existed on spawn_agent_job and now also on restart_agent_job (forwarded through to spawn). Other fixes from review: - README status column: three tests flipped from xfail to green now that the #2644 / #2655 production fixes ship in this PR. - wait_for_job_gone: distinguish 404 via ApiException.status (not substring on str(exc)); log non-404 ApiException and non-ApiException errors so silent timeout doesn't mask RBAC denials or 5xxs. - wait_for_job_gone unit tests: patch time.sleep so the suite isn't paying 0.5s/iteration for the real poll interval; add a 500-status test pinning that only ApiException.status == 404 returns True. - conftest.py: drop the EGG_LAUNCHER_SECRET env-var dance — launcher_secret is passed explicitly to GatewayClient, so the env-var fallback never fires and the mutation/restore was just a parallel-fixture risk. - test_each_slice_gets_its_own_branch_env: actually run the three slice spawns in parallel via ThreadPoolExecutor so a regression in shared spawner state would surface (was sequential despite the "concurrent" naming). - test_long_name_round_trip.py: adopt the cleanup_jobs autouse-fixture pattern the README's "Conventions" section calls out, so an interrupted test still cleans up its Job. Also switch the poll loop to ApiException.status == 404 to match the production helper. - test_kubernetes_client.py: direct unit tests for _normalize_k8s_job_name covering prefix idempotence, the 63-char cap, the SHA-1 digest tail, and the trailing-hyphen guard — so a future refactor that drifts the truncation math is caught at unit-test time.
|
Thanks for the thorough review. Addressed in commit Blocking
Non-blocking
Things that look rightThanks for the confirmation on — Authored by egg |
This comment has been minimized.
This comment has been minimized.
…feedback fixes Pulls in the review-feedback commit (09e08b1) and resolves the conftest.py conflict by taking the simplified spawner fixture (drop EGG_LAUNCHER_SECRET env-var dance) while keeping the BRC consensus fixtures merged in from main.
Conflict Resolution SummaryResolved merge conflicts with
A second merge was then needed to pick up Please review:
Local verification: — Authored by egg |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review
Reviewed the delta since eb70237 plus the merge from main (1152f9efa).
All four blocking + non-blocking items from my prior review are fixed, and the merge from main (which pulled in the BRC consensus regression tests from #2635) integrated cleanly with both fixture sets and a combined docstring.
Feedback addressed
| Prior item | Resolution |
|---|---|
Blocking 1 — kubernetes_spawner.py:487-498 degraded-gateway bypass |
Reverted (kubernetes_spawner.py:484-489). The production check now raises KubernetesSpawnError for any non-healthy gateway regardless of mode. The integration tests now pass wait_for_gateway=False directly (newly forwarded through restart_agent_job, kubernetes_spawner.py:1179). The two unit tests that confirmed the buggy assumption are removed. |
Blocking 2 — integration_tests/regression/README.md status column |
All three rows flipped to ✅ green (integration_tests/regression/README.md:19-21), with parenthetical notes recording the prior xfail status so the audit trail is preserved. |
Non-blocking 3 — wait_for_job_gone swallowed non-404 exceptions silently |
Now distinguishes ApiException.status == 404 (typed) from other ApiException (5xx / RBAC denials — logged) and falls back to substring matching only for monkey-patched test stubs (also logged). kubernetes_client.py:750-794. |
Non-blocking 4 — real time.sleep(0.5) in unit tests |
time.sleep patched in test_returns_true_after_job_disappears, test_returns_false_on_timeout, and the new test_non_404_api_exception_keeps_polling_and_times_out. |
Non-blocking 5 — dead EGG_LAUNCHER_SECRET env-var dance |
Removed (integration_tests/regression/conftest.py:180-194). os import dropped. |
| Non-blocking 6 — "concurrent" but sequential test | Now spawns via concurrent.futures.ThreadPoolExecutor(max_workers=3) (test_slice_spawn_env_threading.py:139-152). |
Non-blocking 7 — no autouse cleanup on test_long_name_round_trip.py |
cleanup_jobs autouse fixture + created_jobs tracking list now match the README's "Conventions" pattern. |
Non-blocking 8 — no direct unit tests for _normalize_k8s_job_name |
Six new tests in TestNormalizeK8sJobName covering short-name prefix, idempotence (×2), the 63-char cap + SHA-1 digest tail, prefix-less inputs, and the trailing-hyphen guard. The math (name[:54].rstrip("-") + "-" + digest[:8]) is now pinned at unit-test time. |
Non-blocking 9 — unused exc in wait_for_job_gone |
Now used in both branches (exc.status / exc.reason / str(exc)). |
New code reviewed
kubernetes_spawner.py:1179— newwait_for_gateway: bool = Trueparameter onrestart_agent_job, default preserves prior behavior, forwarded tospawn_agent_jobat line 1328. Mirrorsspawn_overseer_job's existing pattern (line 1495). Backwards-compatible.kubernetes_client.py:750-794wait_for_job_gone— the two-branch exception handler is correct.except ApiExceptioncatches first (typed Pythontry/exceptordering), theexcept Exceptionfallback only fires for non-ApiExceptionerrors (test stubs, urllib3 transients). Both branches log; only 404 paths returnTrue.TestNormalizeK8sJobNamemath verification:test_long_name_truncated_with_digest:len("egg-sandbox-" + "x"*58) == 70;name[:54]= 54 non-hyphen chars (no rstrip), result = 54 + 1 + 8 = 63 ✓test_long_name_trailing_hyphen_stripped_before_digest:name[:54]ends with "-",rstrip("-")yields 53 chars, result = 53 + 1 + 8 = 62. Thersplit("-", 1)correctly recovers the readable head ✓
test_each_slice_gets_its_own_branch_env—ThreadPoolExecutor(max_workers=3)withas_completed, results gathered into adict[slice_id, pod], then assertions iterateslicesin fixed order for readable assertion failures. Correct.- Merge resolution in
conftest.py— k3s slice helpers (kubectl_get_pod_yaml,env_from_pod,spawner) and BRC consensus fixtures (_reset_tracker_registryautouse,event_capture,filter_events, three review-graph fixtures) coexist; combined sys.path setup (_REGRESSION_DIR,orchestrator/,shared/, root) supports both tiers; combined docstring documents both tiers and when each fires. The_reset_tracker_registryautouse fixture is a no-op for slice-spawn tests (they don't touch trackers), so the only cost is the per-test dict snapshot — acceptable.
Verified correct
- The
wait_for_gateway=Falsepath in spawn_agent_job bypasses the health gate but still routes through the rest of the spawn flow includinggateway.create_worktrees(gated behindif repos:—repos=[]in the regression tests skips it). - All slice-restart invariant assertions (
EGG_BRANCH,EGG_SLICE_ID, Job-name distinctness, restart-budget isolation) are preserved across the parameter additions. - CI shows all required checks green:
Integration Tests / Aggregate,Unit Tests,Security Scan,aggregate.
Non-blocking note
restart_agent_job's new wait_for_gateway parameter has no direct unit test asserting forwarding to spawn_agent_job — the integration tests exercise it end-to-end, but a unit-tier test (mock_spawn.assert_called_with(wait_for_gateway=False, ...)) would catch a regression where the forwarding gets accidentally dropped during a future refactor. Mirror test_spawn_without_gateway_wait (line 477) into a restart variant. Not blocking — the integration suite covers it.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Adds two unit tests asserting that restart_agent_job forwards the wait_for_gateway kwarg through to spawn_agent_job (False and the True default). Mirrors test_spawn_without_gateway_wait so a future refactor that drops the forwarded kwarg fails at unit-test time. Addresses review note on PR #2651.
|
Thanks for the re-review and the approval. Addressed the one outstanding non-blocking note in commit
Local: — Authored by egg |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review
Reviewed the delta since b7c1aaf — one new commit (1056191) adding the two unit tests I suggested as the sole non-blocking note from my prior approve-with-suggestions review.
Feedback addressed
| Prior item | Resolution |
|---|---|
Non-blocking — no direct unit test for restart_agent_job's wait_for_gateway forwarding |
Fixed (orchestrator/tests/test_kubernetes_spawner.py:1109-1135). Two tests added under TestRestartAgentJob: test_restart_forwards_wait_for_gateway_false and test_restart_forwards_wait_for_gateway_default_true. Both patch spawner.spawn_agent_job and assert mock_spawn.call_args.kwargs["wait_for_gateway"] matches the input. |
Verified correct
- Pattern matches the existing
test_spawn_without_gateway_wait(line 477) — local unit-test seam pinning a forwarded kwarg. - Mock interaction is sound. The
mock_k8s_clientfixture setsdelete_job.side_effect = PodNotFoundError(...)sodelete_attemptedstaysFalseandwait_for_job_goneis not invoked during the test — the pre-spawn cleanup path doesn't interfere with the kwarg assertion. - Both directions are covered: a refactor that hardcodes
wait_for_gateway=Trueis caught by the False test; a hardcode toFalseis caught by the default-True test; dropping the kwarg entirely raisesKeyErrorfromcall_args.kwargs[...], failing both tests. patch.object(spawner, "spawn_agent_job")correctly intercepts the bound method on the instance, soself.spawn_agent_job(...)insiderestart_agent_jobroutes to the mock. The mock's auto-created.container_info.job_namesatisfies the trailinglogger.info(..., new_job_name=spawned.container_info.job_name, ...)without a realSpawnedContainerreturn.- No new code in production paths — test-only change. Lint/test footprint is unchanged from the prior approval.
Approving. Everything from the prior review still holds; the originally-requested forwarding seam is now pinned at the unit tier.
— Authored by egg
|
egg review completed. View run logs 13 previous review(s) hidden. |
Summary
Adds
integration_tests/regression/— a new home for k3s-level regression guards that pin invariants the SDLC pipeline has regressed historically — and the three k3s-spawner fixes those guards surfaced so the tests ship green.Regression tests
test_slice_spawn_env_threading.py(green): three concurrent slice agents, each spawned with a conflicting pipeline-levelEGG_BRANCHinextra_env(the Slice-coder agents spawned with EGG_BRANCH=<id>/work instead of <id>/slice-N — pushes rejected by gateway #2428 repro shape). Asserts every pod'sEGG_BRANCHmatches its own slice ref andEGG_SLICE_IDmatches the slice id — i.e._PROTECTED_ENV_KEYSis doing its job and the per-slice spawn doesn't get clobbered. Also pins sibling-isolation: distinct Job names per slice (Slice agents spawned with invalid EGG_PIPELINE_ID='<id>/slice-N' and no worktree mount #2403).test_slice_restart_branch_invariants.py(now green — wasxfailon k3s: KubernetesClient.delete_job doesn't apply 63-char name truncation, breaks restart_agent_job for long names #2644 + k3s: restart_agent_job races Foreground deletion finalizer, 409s respawn #2655): the slice-restart half of Integration test coverage: slice spawn & DAG execution #2632. Pins thatrestart_agent_jobfor a slice preservesEGG_BRANCH/EGG_SLICE_IDon the new pod, and that restarting a pipeline-level agent of the same role doesn't disturb the slice-scoped Job or its restart budget.test_long_name_round_trip.py(now green — wasxfailon k3s: KubernetesClient.delete_job doesn't apply 63-char name truncation, breaks restart_agent_job for long names #2644): pinscreate_container+delete_jobround-trip for >63-char Job names.README.mddocuments the audit: what's covered, what's deferred (and why — mostly ScriptedProvider pod-injection infra per Expand integration test coverage #2474).Production fixes (this commit)
delete_jobname truncation. ExtractKubernetesClient._normalize_k8s_job_namefromcreate_containerand apply it indelete_job/get_pod_for_job. Long names now round-trip symmetrically: callers (e.g.KubernetesSpawner.restart_agent_jobconstructing the un-truncated form via_build_k8s_job_names) hit the truncated on-server name.KubernetesClient.wait_for_job_goneand call it fromrestart_agent_jobbetween the Foreground delete and the respawn. Without the wait, the immediate respawn raced the deletion finalizer and 409'd onAlreadyExists.LABEL_SLICE_ID(egg.slice.id) constant; apply it conditionally on slice-scoped spawns; addKubernetesSpawner.list_slice_jobs(pipeline_id, slice_id).Unit coverage
Four new tests in
orchestrator/tests/test_kubernetes_client.py: long-name normalization indelete_job, create+delete round-trip with the same un-truncated input, and the threewait_for_job_gonestates (already-gone / disappears-during-poll / timeout / long-name normalization).Constraint
Per #2474 the SDLC pipeline can't run integration tests; correctness for
integration_tests/regression/is verified by theTest / aggregaterequired check on this PR.Test plan
make lintclean (ruff, format, mypy)make testclean on the orchestrator unit tests (120 intest_kubernetes_client.py, 100 intest_kubernetes_spawner.py)Test / aggregategreen (drivesmake test-integration; the three previously-xfail tests now expected to pass)Closes #2644
Closes #2655
Closes #2666