Skip to content

fix: stop adding SparkApplication owner reference to executor pods - #3152

Merged
google-oss-prow[bot] merged 2 commits into
kubeflow:masterfrom
KR-Ravindra:fix/executor-pods-no-sparkapp-owner-ref
Sep 14, 2026
Merged

google-oss-prow[bot] merged 2 commits into
kubeflow:masterfrom
KR-Ravindra:fix/executor-pods-no-sparkapp-owner-ref

Conversation

@KR-Ravindra

@KR-Ravindra KR-Ravindra commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Purpose of this PR

Proposed changes:

  • Stop adding the SparkApplication as a second (non-controller) ownerReference on executor pod templates at submission time, so that executor pods are garbage-collected as soon as their controller owner (the driver pod) is deleted.
  • Update the expectations in TestExecutorPodTemplateContents and TestSubmitPodTemplates so executor templates carry only user-supplied owner references; driver templates keep the controller reference to the SparkApplication (TestDriverPodTemplateContents is unchanged).

Problem

Since #2670 every executor pod has two owners: the driver pod (controller: true, set by Spark itself) and the SparkApplication (controller unset, added by the operator). Kubernetes garbage collection only deletes a dependent once all of its owners are gone, so deleting the driver pod (manually, or via spec.suspend=true, e.g. when Kueue preempts the workload) leaves the executor pods behind until the SparkApplication itself is deleted. Across Kueue requeue cycles this accumulates orphaned executor pods.

Root cause

internal/controller/sparkapplication/submission.go:1199-1209 (buildExecutorPodTemplate) appends util.GetOwnerReference(app) with Controller/BlockOwnerDeletion cleared to the executor pod template. That extra owner is what blocks the cascade deletion from the driver pod. The webhook path (internal/webhook/sparkpod_defaulter.go addOwnerReference) already returns early for non-driver pods, so submission is the only place that adds it.

Fix

Remove the executor ownerReference block in buildExecutorPodTemplate. Executor pods keep their controller reference to the driver pod, and the driver pod keeps its controller reference to the SparkApplication, so deleting the SparkApplication still cascades to the driver and then to the executors, while deleting only the driver now cascades to the executors as well. This follows the approach suggested in the issue discussion (drop the executor's SparkApplication reference and rely on GC cascade deletion instead of adding operator-managed deletion of executor pods).

The original motivation for the extra reference was to let other controllers (e.g. Kueue) recognize executor pods as children of the SparkApplication. Kueue's ownership walk (FindAncestorJobManagedByKueue) and its IsOwnerManagedByKueueForObject check both use metav1.GetControllerOf, i.e. only controller references, and its SparkApplication integration identifies executor pods by the spark-role label and reads readiness from status.executorState, so the non-controller reference is not consumed there.

How tested

Unit tests (no cluster required) plus an e2e assertion in the existing suspend/resume test.

Before the fix, with the updated expectations, three subtests fail. Two are shown below; the third, TestSubmitPodTemplates/sends_default_templates_with_owner_ref_when_not_specified (internal/controller/sparkapplication/rest_submission_test.go:203), fails the same way because the submitted executor template still carries the SparkApplication owner reference (Empty assertion on OwnerReferences) and is only absent from the output because of the -run filter. The "pod template already has owner references" case is unchanged and passes both before and after, because user-supplied owner references are preserved as-is.

$ go test ./internal/controller/sparkapplication/ -run 'TestExecutorPodTemplateContents|TestDriverPodTemplateContents' -count=1
--- FAIL: TestExecutorPodTemplateContents (0.00s)
    --- FAIL: TestExecutorPodTemplateContents/with_pod_template (0.00s)
        submission_test.go:645:
            	Diff:
            	--- Expected
            	+++ Actual
            	@@ -6,2 +6,6 @@
            	     uid: ""
            	+  - apiVersion: sparkoperator.k8s.io/v1beta2
            	+    kind: SparkApplication
            	+    name: test
            	+    uid: 2d2d2c08-fcc9-4e41-9d93-13540605f24e
            	 spec:
    --- FAIL: TestExecutorPodTemplateContents/no_pod_template (0.00s)
        submission_test.go:645:
            	Diff:
            	--- Expected
            	+++ Actual
            	@@ -1,2 +1,7 @@
            	-metadata: {}
            	+metadata:
            	+  ownerReferences:
            	+  - apiVersion: sparkoperator.k8s.io/v1beta2
            	+    kind: SparkApplication
            	+    name: test
            	+    uid: 2d2d2c08-fcc9-4e41-9d93-13540605f24e
            	 spec:
FAIL
FAIL	github.com/kubeflow/spark-operator/v2/internal/controller/sparkapplication	0.031s

After the fix:

$ go test ./internal/controller/sparkapplication/ -run 'TestExecutorPodTemplateContents|TestDriverPodTemplateContents' -count=1 -v
--- PASS: TestDriverPodTemplateContents (0.00s)
--- PASS: TestExecutorPodTemplateContents (0.00s)
ok  	github.com/kubeflow/spark-operator/v2/internal/controller/sparkapplication	0.030s

$ KUBEBUILDER_ASSETS=... go test ./internal/controller/sparkapplication/... ./internal/webhook/... -count=1
ok  	github.com/kubeflow/spark-operator/v2/internal/controller/sparkapplication
ok  	github.com/kubeflow/spark-operator/v2/internal/webhook

gofmt -l, go vet, go build ./... and golangci-lint run ./internal/controller/sparkapplication/... are clean.

e2e (added on request of the reviewers): the spark-pi: Suspend and Resume case in test/e2e/sparkapplication_test.go now waits for an executor pod to exist before suspending and, once the application reaches Suspended, asserts that no pod labelled sparkoperator.k8s.io/app-name=spark-pi remains (driver or executor). Without this fix the executor pod survives the driver deletion because the SparkApplication is still an owner, so the assertion fails; with the fix the executors are cascade-deleted with the driver. gofmt -l and go vet ./... in test/e2e are clean; the CI e2e matrix runs it.

Links

Change Category

  • Bugfix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that could affect existing functionality)
  • Documentation update

Rationale

Covered above. Executor pods created by an operator built before this change keep their existing owner references; only newly submitted applications are affected.

Checklist

  • I have conducted a self-review of my own code.
  • I have updated documentation accordingly.
  • I have added tests that prove my changes are effective or that my feature works.
  • Existing unit tests pass locally with my changes.

Additional Notes

This change was prepared with an AI agent operated by KR-Ravindra, who reviewed and tested it.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

🎉 Welcome to the Kubeflow Spark Operator! 🎉

Thanks for opening your first PR! We're happy to have you as part of our community 🚀

Here's what happens next:

Join the community:

Feel free to ask questions in the comments if you need any help or clarification!
Thanks again for contributing to Kubeflow! 🙏

@KR-Ravindra

Copy link
Copy Markdown
Contributor Author

Round 1 self-review (AI agent operated by me; I review every revision). Marking ready for review because:

  1. Root cause verified on master: buildExecutorPodTemplate (submission.go:1199-1209) appends a non-controller SparkApplication owner reference to every executor template; the webhook's addOwnerReference returns early for non-driver pods, so this is the only source. Kubernetes GC keeps a dependent while any owner exists, so the driver pod's deletion cannot cascade.
  2. This is the approach a reviewer suggested on Executor pods are not cascade-deleted when driver pod is deleted #2961 (drop the executor reference and rely on GC cascade, instead of operator-managed executor deletion).
  3. The Kueue claim in the body checks out: FindAncestorJobManagedByKueue walks metav1.GetControllerOf (kueue reconciler.go:933), and the SparkApplication integration selects executors by the spark-role label, so the non-controller reference is not consumed there.
  4. slices / reflect stay imported for the driver template path (:1175); DCO, FOSSA and readthedocs pass.

Open question for maintainers: #2670 added the reference for Kueue; if any other consumer relies on it, the alternative is an explicit executor cleanup on driver deletion.

@KR-Ravindra
KR-Ravindra marked this pull request as ready for review September 8, 2026 02:49
Copilot AI balanced review requested due to automatic review settings September 8, 2026 02:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The change alters Kubernetes ownership/garbage-collection semantics with cross-controller (Kueue) implications that warrant final human verification.

Pull request overview

This PR fixes a garbage-collection bug (issue #2961) where executor pods were not cascade-deleted when only the driver pod was removed (e.g. on spec.suspend=true via Kueue preemption). The root cause was a second, non-controller ownerReference pointing at the SparkApplication that the operator added to executor pod templates at submission time (introduced in #2670). Since Kubernetes GC only deletes a dependent once all owners are gone, this blocked the cascade from the driver pod. The fix removes that extra owner reference so executors rely solely on their controller reference to the driver pod.

Changes:

  • Remove the block in buildExecutorPodTemplate that appended a non-controller SparkApplication owner reference to executor pod templates, replacing it with an explanatory comment.
  • Update TestExecutorPodTemplateContents and TestSubmitPodTemplates so executor templates carry only user-supplied owner references.
File summaries
File Description
internal/controller/sparkapplication/submission.go Drops the executor-template SparkApplication owner reference; driver template still gets its controller reference.
internal/controller/sparkapplication/submission_test.go Removes the expected SparkApplication owner reference from executor template expectations; user-supplied refs preserved.
internal/controller/sparkapplication/rest_submission_test.go Asserts executor pod template now carries no owner references.

I verified that the slices, reflect, and metav1 imports remain in use by buildDriverPodTemplate, the appNonControllerOwnerReference test helper is still referenced by the "already has owner references" case, the webhook's addOwnerReference returns early for non-driver pods (so the reference is not re-added), and GetOwnerReference still sets Controller: true for the driver template. No issues were found.

Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Since kubeflow#2670 executor pods carried two owners: the driver pod
(controller=true, set by Spark) and the SparkApplication
(non-controller, added at submission time). Kubernetes garbage
collection only deletes a dependent once all owners are gone, so
deleting the driver pod (manual delete, spec.suspend=true, Kueue
preemption) left executor pods orphaned until the SparkApplication
itself was deleted.

Drop the extra owner reference so executors are cascade-deleted with
the driver pod. Deleting the SparkApplication still cascades through
the driver pod to the executors.

Issue: kubeflow#2961
Signed-off-by: KR Ravindra <42912207+KR-Ravindra@users.noreply.github.com>
@KR-Ravindra
KR-Ravindra force-pushed the fix/executor-pods-no-sparkapp-owner-ref branch from bcabf35 to c6c3816 Compare September 8, 2026 03:01

@RobuRishabh RobuRishabh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hi @KR-Ravindra thanks for the contribution, the PR looks good to me
@tariq-hasan need a suggestion here unit tests are enough for merge but how about add a e2e test here that submits an app, deletes the driver, and asserts executors disappear would lock in the fix or do you think its over-engineering.

and a quick question if anyone has answer to it, Is there any other tool (not Kueue, not spark-operator) that depends on executors having a direct SparkApplication owner ref?

@RobuRishabh

Copy link
Copy Markdown
Contributor

/ok-to-test

@KR-Ravindra

Copy link
Copy Markdown
Contributor Author

@RobuRishabh thanks for the review. Happy to add the e2e test if @tariq-hasan wants it: a case in test/e2e/sparkapplication_test.go that submits an application, deletes the driver pod once the executors are running, and asserts the executor pods are gone within the GC timeout. It is a few dozen lines on top of the existing helpers, so I would not call it over-engineering. Say the word and I will push it to this PR.

On the ownerRef question: executor pods keep the owner reference Spark itself sets (the driver pod, controller: true), so anything that walks ownerReferences (kubectl tree, the Argo CD resource tree) still reaches the executors through the driver. Only the direct executor -> SparkApplication edge goes away. I am not aware of a tool that depends on that direct edge; the operator's own executor accounting is label-based (sparkoperator.k8s.io/app-name), and Spark's own cleanup uses the driver reference.

@tariq-hasan

Copy link
Copy Markdown
Member

I'm thinking we might be able to add assertions here to test the change.

By("Waiting for SparkApplication to Suspended")
Eventually(func(g Gomega) {
app := &v1beta2.SparkApplication{}
g.Expect(k8sClient.Get(ctx, key, app)).To(Succeed())
g.Expect(app.Status.AppState.State).To(Equal(v1beta2.ApplicationStateSuspended))
}).WithTimeout(3 * time.Minute).Should(Succeed())

For the use cases I'm thinking we do not really have a clearly defined one: #2670 (comment). We can always revisit if we do see actual use cases that require this owner reference.

@vjanelle @vikas-saxena02 Would appreciate further thoughts on this.

Extend the spark-pi suspend/resume e2e test: wait for an executor pod to
exist before suspending, then after the application reaches Suspended
assert that no driver or executor pods labelled with the application
name remain. With the extra SparkApplication owner reference on
executors this assertion fails because the executor pods survive the
driver pod deletion.

Signed-off-by: KR Ravindra <42912207+KR-Ravindra@users.noreply.github.com>
@google-oss-prow google-oss-prow Bot added size/M and removed size/S labels Sep 9, 2026
@KR-Ravindra

Copy link
Copy Markdown
Contributor Author

@tariq-hasan done in 2b4fb02: the suspend/resume e2e case now waits for an executor pod to exist before suspending and, after the app reaches Suspended, asserts that no pod labelled sparkoperator.k8s.io/app-name=spark-pi remains. On master that assertion fails because the executor pod survives the driver deletion; with this change the executors are cascade-deleted with the driver. Could you take another look once the e2e matrix has run?

@RobuRishabh

Copy link
Copy Markdown
Contributor

Thanks @KR-Ravindra for the contribution.
/lgtm

@vikas-saxena02

Copy link
Copy Markdown
Contributor

I'm thinking we might be able to add assertions here to test the change.

By("Waiting for SparkApplication to Suspended")
Eventually(func(g Gomega) {
app := &v1beta2.SparkApplication{}
g.Expect(k8sClient.Get(ctx, key, app)).To(Succeed())
g.Expect(app.Status.AppState.State).To(Equal(v1beta2.ApplicationStateSuspended))
}).WithTimeout(3 * time.Minute).Should(Succeed())

For the use cases I'm thinking we do not really have a clearly defined one: #2670 (comment). We can always revisit if we do see actual use cases that require this owner reference.

@vjanelle @vikas-saxena02 Would appreciate further thoughts on this.

Agree with dropping the reference — I think this is the right direction, not just the
expedient one, and I'd be happy to see this merge as-is.

Executors already carry the owner reference Spark itself sets (driver pod,
controller: true). #2670 added a second edge the operator invented, and since GC only
reaps a dependent once every owner is gone, that edge structurally prevents any partial
cascade. Removing it puts us back on the upstream ownership graph, which is the one Spark
maintains for us rather than one we have to keep correct ourselves.

Worth noting the fix is wider than the reported bug: every path that removes the driver
without removing the SparkApplication was leaking executors — suspend/Kueue preemption,
restartPolicy retries, and node-lifecycle deletion of the driver pod.

@tariq-hasan there are three things I'd like on the record, but none of them are things I
think we should load onto this PR — @KR-Ravindra has already turned around two rounds
quickly and each of these needs research beyond the change itself. Your call whether they
become follow-up issues or you'd rather fold any of them in here:

  1. Orphan-policy deletes. After this change, an orphan-policy delete of the driver
    (kubectl delete pod <driver> --cascade=orphan, or a controller doing the same) leaves
    executors with no owner at all, where previously the SparkApplication reference would
    still reap them. Niche, and I'd accept it as the cost of the fix. If it ever surfaces,
    the mitigation is a finalizer-based sweep on SparkApplication deletion keyed on
    sparkoperator.k8s.io/app-name, which the operator already tracks executors by.

  2. Kueue confirmation. Set ControllerReference on driver pod and non-Controller OwnerReference on executor pod at submission time #2670 was merged for Kueue specifically, so I'd rather have
    that closed out by the Kueue side on SparkApplication framework: PodsReady ignores executor state, and Stop leaves orphan executor pods across requeue cycles kubernetes-sigs/kueue#11675 than inferred from
    their source. FindAncestorJobManagedByKueue walking GetControllerOf reads correctly
    to me, but that's a cross-project conversation and shouldn't be a contributor's job to
    chase.

  3. A GC-specific e2e case. The assertion added to suspend/resume does catch the
    current bug, but it passes whether executors were removed by GC cascade or by
    operator-managed deletion — so if the suspend path is ever reimplemented as explicit
    cleanup, it stays green while this regresses silently. A dedicated case (submit, wait
    for executors Running, delete only the driver pod, Eventually assert executors
    gone) pins the semantics Executor pods are not cascade-deleted when driver pod is deleted #2961 is actually about.

Happy to take (1) and (2) as follow-ups myself if that's useful.

@KR-Ravindra

Copy link
Copy Markdown
Contributor Author

@vikas-saxena02 thanks, that matches my understanding of the ownership graph. On (3): agreed, the suspend assertion cannot tell a GC cascade from explicit cleanup. I can add the dedicated case (submit, wait for executors Running, delete only the driver pod, Eventually assert the executors are gone) to this PR or as a follow-up PR after this merges, whichever @tariq-hasan prefers. Taking (1) and (2) as follow-up issues sounds right to me.

@vjanelle

vjanelle commented Sep 10, 2026 via email

Copy link
Copy Markdown
Contributor

@KR-Ravindra

Copy link
Copy Markdown
Contributor Author

@vjanelle I read it the same way. --cascade=orphan is an explicit request to strip the dependents' owner references, so executors ending up ownerless is the documented outcome of that call, and it is also what happened before #2670, when executors only carried the driver reference Spark sets. I don't think this PR should special-case it. If the label-based sweep vikas described is wanted later, it fits a follow-up.

@vjanelle

Copy link
Copy Markdown
Contributor

Also consider clusters with 10s of thousands of executors, if it's a one time scan maybe, but not continuous.

@KR-Ravindra

Copy link
Copy Markdown
Contributor Author

@vjanelle agreed. If that sweep is ever built it should be a one-time pass triggered by the SparkApplication deletion (finalizer), listing by the sparkoperator.k8s.io/app-name label once, not a continuous scan. Nothing in this PR adds any scanning; it only removes the owner reference.

@vikas-saxena02

Copy link
Copy Markdown
Contributor

An orphan cascade delete intentionally means ownership is being broken, do we need to handle that?
…
On Wed, Sep 9, 2026 at 5:07 PM KR Ravindra @.> wrote: KR-Ravindra left a comment (kubeflow/spark-operator#3152) <#3152 (comment)> @vikas-saxena02 https://github.com/vikas-saxena02 thanks, that matches my understanding of the ownership graph. On (3): agreed, the suspend assertion cannot tell a GC cascade from explicit cleanup. I can add the dedicated case (submit, wait for executors Running, delete only the driver pod, Eventually assert the executors are gone) to this PR or as a follow-up PR after this merges, whichever @tariq-hasan https://github.com/tariq-hasan prefers. Taking (1) and (2) as follow-up issues sounds right to me. — Reply to this email directly, view it on GitHub <#3152?email_source=notifications&email_token=AAFTDSNA4J7NXUL6DRLKLN35OHWELA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNRRGA2TINJTG4Z2M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5610545373>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAFTDSK5MGZGOJH2YGCMDE35OHWELAVCNFSNUABFKJSXA33TNF2G64TZHMYTCNRRGY2TCOBYHNEXG43VMU5TKMZYGEZDEMBZGUZKC5QC . You are receiving this because you were mentioned.Message ID: @.>

I may be overthinking but it could be due user mistakes, happy to drop this if other think the same way!!

@KR-Ravindra

Copy link
Copy Markdown
Contributor Author

@vikas-saxena02 works for me. If a real user-mistake case turns up later, the one-time label-based sweep is the fix and can go in as a follow-up.

@vikas-saxena02

Copy link
Copy Markdown
Contributor

@vikas-saxena02 works for me. If a real user-mistake case turns up later, the one-time label-based sweep is the fix and can go in as a follow-up.

I actually had this at a client side when one of my guys, did so based on chatgpt advise :D

@vikas-saxena02

Copy link
Copy Markdown
Contributor

@KR-Ravindra the changes look good to me

@vara-bonthu vara-bonthu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

/approve

@google-oss-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: vara-bonthu

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@google-oss-prow
google-oss-prow Bot merged commit a0f33cc into kubeflow:master Sep 14, 2026
20 of 21 checks passed
shruthis4 pushed a commit to opendatahub-io/spark-operator that referenced this pull request Sep 17, 2026
* fix: preserve shell-sensitive Spark Connect configuration values (kubeflow#3051)

* fix: preserve shell-sensitive Spark Connect configuration values

Signed-off-by: Jay Bharadwaj <jbharadw@openai.com>

* refactor: improve Spark Connect shell quoting readability

Signed-off-by: Jay Bharadwaj <jbharadw@openai.com>

---------

Signed-off-by: Jay Bharadwaj <jbharadw@openai.com>

* Chore(Remove go card from readme) - kubeflow#3058 (kubeflow#3060)

Signed-off-by: Tamir Krasna <64045253+Tankdoggo@users.noreply.github.com>

* chore(deps): bump github.com/prometheus/common from 0.67.5 to 0.70.1 in /test/e2e (kubeflow#3083)

Bumps [github.com/prometheus/common](https://github.com/prometheus/common) from 0.67.5 to 0.70.1.
- [Release notes](https://github.com/prometheus/common/releases)
- [Changelog](https://github.com/prometheus/common/blob/main/CHANGELOG.md)
- [Commits](prometheus/common@v0.67.5...v0.70.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/common
  dependency-version: 0.70.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump actions/checkout from 6.0.2 to 7.0.1 (kubeflow#3086)

Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](actions/checkout@v6.0.2...v7.0.1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(ci): Publish Spark Operator binaries with GitHub releases (kubeflow#3078)

* initial commit

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* feat(ci): publish operator binaries with GitHub releases

Cross-build spark-operator for linux/amd64 and linux/arm64 in the
draft_release job, package with checksums, and attach to the release.

Closes kubeflow#3073

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* fixing typo as part of my previous PR kubeflow#3056

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* Update .github/workflows/release.yaml

Co-authored-by: Tariq Hasan <mmtariquehsn@gmail.com>
Signed-off-by: Vikas Saxena <90456542+vikas-saxena02@users.noreply.github.com>

* fix(ci): stage release binaries outside the repo and harden verification

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* fix(ci): entrypoint was not part of tar ball so adding it

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* fix(build): read VERSION from file so an inherited env var cannot double the v prefix

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

---------

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>
Signed-off-by: Vikas Saxena <90456542+vikas-saxena02@users.noreply.github.com>
Co-authored-by: Tariq Hasan <mmtariquehsn@gmail.com>

* feat: garbage-collect terminated SparkApplications via operator default TTL (kubeflow#3042)

* feat: add DefaultTimeToLive alpha feature gate

Signed-off-by: dineshkumar181094 <dineshkumar181094@gmail.com>

* feat: add EffectiveTimeToLiveSeconds TTL resolution helper

Signed-off-by: dineshkumar181094 <dineshkumar181094@gmail.com>

* feat: add --default-time-to-live-seconds controller flag

Signed-off-by: dineshkumar181094 <dineshkumar181094@gmail.com>

* feat: apply operator default TTL when reconciling terminated SparkApplications

Signed-off-by: dineshkumar181094 <dineshkumar181094@gmail.com>

* feat: expose default TTL via Helm chart

Signed-off-by: dineshkumar181094 <dineshkumar181094@gmail.com>

* refactor: unify TTL expiry logic and harden default-TTL edges

Address code review feedback on the operator default-TTL feature:

- Unify expiry into util.IsExpiredWithTTL, the single primitive that takes
  an explicit (possibly defaulted) TTL; IsExpired delegates to it. Treat a
  non-positive spec.timeToLiveSeconds as 'expire immediately' everywhere, so
  an explicit user value always wins over the operator default and behavior
  is unchanged from before this feature.
- Move the expiry decision out of the controller into util; the reconciler
  now calls util.IsExpiredWithTTL with the effective TTL.
- Fix a flaky controller test: drive the app into COMPLETED with a past
  TerminationTime unconditionally (get-or-create) so the precondition holds
  even when the object already exists.
- Render --default-time-to-live-seconds only when the value is > 0 so a
  negative value no longer emits the flag; add a chart test for it.

Signed-off-by: dineshkumar181094 <dineshkumar181094@gmail.com>

* feat: make DefaultTimeToLive gate authoritative and consistent

Address further review feedback on the operator default-TTL feature:

- Gate the Helm-rendered --default-time-to-live-seconds flag on both the
  DefaultTimeToLive feature gate (new spark-operator.defaultTimeToLive.enabled
  helper, mirroring the RestSubmitter one) and a positive value, so the chart
  no longer emits a flag the controller would ignore at runtime.
- Validate the flag at controller startup: error in PreRunE when the gate is
  enabled but the value is non-positive, and warn (after the logger is set up)
  when the value is set but the gate is disabled.
- Log the operator-default provenance only when the app is actually deleted,
  instead of on every reconcile of a terminated app.
- Make the event filter's resync guard use the effective TTL so an app expired
  via the operator default is re-admitted for deletion, consistent with the
  controller's cleanup decision; thread defaultTimeToLiveSeconds through the
  event filter and add coverage.

Signed-off-by: dineshkumar181094 <dineshkumar181094@gmail.com>

* chore: add DefaultTimeToLive feature gate to manager manifest

The generated controller manifest enumerates all feature gates in its
--feature-gates arg. Add DefaultTimeToLive=false so the manifest matches
the registered gates and the drift check passes.

Signed-off-by: dineshkumar181094 <dineshkumar181094@gmail.com>

* fix: reject only negative default TTL and cover the not-yet-expired requeue path

Refine the --default-time-to-live-seconds validation and add missing test
coverage for the operator default TTL:

- Scope the PreRunE guard to negative values only. A negative TTL is never
  meaningful and is now rejected regardless of the feature gate, while 0
  remains the valid 'off' sentinel (even when the gate is enabled) rather
  than a hard startup error. Update the flag help to match.
- Add a controller test for a terminated app that has not yet outlived the
  operator default TTL (terminated 30s ago, default 60s): assert it is not
  deleted and is requeued via RequeueAfter computed from the effective TTL.

Signed-off-by: dineshkumar181094 <dineshkumar181094@gmail.com>

* feat: render a zero default TTL as a valid Helm flag and cover it

Make defaultTimeToLiveSeconds=0 a valid, explicitly-rendered value rather
than a silently-omitted one:

- Change the deployment template guard from 'gt ... 0' to 'ge ... 0' so a
  zero value renders --default-time-to-live-seconds=0 when the gate is
  enabled. Negative values are still omitted to avoid the binary's startup
  rejection. Update the chart test for the zero case accordingly.
- Add a controller test proving that with the gate enabled and a zero
  default TTL, a terminated SparkApplication is neither deleted nor
  requeued (the zero value disables the default, as intended).

Signed-off-by: dineshkumar181094 <dineshkumar181094@gmail.com>

* refactor: normalize default TTL configuration

Signed-off-by: dineshkumar181094 <dineshkumar181094@gmail.com>

---------

Signed-off-by: dineshkumar181094 <dineshkumar181094@gmail.com>

* chore(deps): bump github.com/onsi/ginkgo/v2 from 2.27.2 to 2.32.1 in /test/e2e (kubeflow#3095)

Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.27.2 to 2.32.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](onsi/ginkgo@v2.27.2...v2.32.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-version: 2.32.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* docs: document webhook failure when a service mesh sidecar is injected (kubeflow#3100)

* docs: document webhook failure when a service mesh sidecar is injected

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* adding caveat for multiple releases

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

---------

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* fix(webhook): generate valid volume names for ConfigMaps (kubeflow#3099)

Signed-off-by: immanuwell <pchpr.00@list.ru>

* feat(ci): publish the operator binary extracted from the released image (kubeflow#3098)

* feat(ci): publish the operator binary extracted from the released image

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* incoperating review comments by Tariq

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* incoperating review comments by Tariq - take 2

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

---------

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* fix(sparkconnect): resolve images from selected template containers (kubeflow#3103)

Signed-off-by: immanuwell <pchpr.00@list.ru>

* docs: add Spark Connect user guide (kubeflow#3091)

Signed-off-by: Vidyansh <ezboi2312@gmail.com>

* feat(controller): add default service account fallback for SparkApplication driver (kubeflow#3092)

* feat(controller): add default service account fallback for SparkApplication driver

Adds a --default-service-account controller flag. When set, SparkApplications
that do not specify .spec.driver.serviceAccount fall back to this value at
submission time. The fallback is applied to a copy of the application so it is
never persisted to the custom resource.

Empty by default, so behaviour is unchanged on upgrade.

Part of kubeflow#3049

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* refactor(controller): move driver service account fallback to pkg/util and honour pod template

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* feat(controller): validate --default-service-account is a valid DNS subdomain

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* chore(chart): group --default-service-account with other controller defaults

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* docs: document the default driver service account flag

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

---------

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* fix(webhook): generate valid volume name for Prometheus ConfigMap (kubeflow#3111)

A SparkApplication name may be up to 63 characters, so appending the
Prometheus ConfigMap and volume suffixes overshoots the volume name limit
and Kubernetes rejects every driver and executor pod of the application.

Signed-off-by: rjgoyln <pt40419@gmail.com>

* refactor: centralize Spark container selection (kubeflow#3115)

* refactor: centralize Spark container selection

Signed-off-by: rohith1X8 <rohith18p18@gmail.com>

* chore: address review comments

Signed-off-by: rohith1X8 <rohith18p18@gmail.com>

---------

Signed-off-by: rohith1X8 <rohith18p18@gmail.com>

* feat(sparkconnect): add service account fallback for Spark Connect server pod (kubeflow#3109)

* feat(sparkconnect): add service account fallback for Spark Connect server pod

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* refactor(sparkconnect): drop server serviceAccount CRD field as asked by Tariq and clarify flag scope

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* refactor(sparkconnect): inline default service account fallback and add log line

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

---------

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* chore(ci): migrate vulnerability scanning to OSV-Scanner (kubeflow#3110)

* chore(ci): migrate vulnerability scanning to OSV-Scanner

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* Update .github/workflows/osv-scanner.yaml

Co-authored-by: Manabu McCloskey <manabu.mccloskey@gmail.com>
Signed-off-by: Vikas Saxena <90456542+vikas-saxena02@users.noreply.github.com>

* chore(ci): remove image scanning job as instructed by @nabuskey

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

---------

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>
Signed-off-by: Vikas Saxena <90456542+vikas-saxena02@users.noreply.github.com>
Co-authored-by: Manabu McCloskey <manabu.mccloskey@gmail.com>

* chore(deps): bump google.golang.org/grpc from 1.82.1 to 1.83.1 in /test/e2e (kubeflow#3136)

Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.82.1 to 1.83.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](grpc/grpc-go@v1.82.1...v1.83.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.83.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump tornado from 6.5.7 to 6.5.8 in /docs/website (kubeflow#3135)

Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.5.7 to 6.5.8.
- [Changelog](https://github.com/tornadoweb/tornado/blob/master/docs/releases.rst)
- [Commits](tornadoweb/tornado@v6.5.7...v6.5.8)

---
updated-dependencies:
- dependency-name: tornado
  dependency-version: 6.5.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Make webhook leader election timings configurable (kubeflow#3065)

* feat(webhook): add leader election parameters for lease duration, renew deadline, and retry period

Signed-off-by: mosa0519 <mohanakrishna.sadhu@netcracker.com>

* feat(webhook): add leader election parameters for lease duration and renew deadline

Signed-off-by: mosa0519 <mohanakrishna.sadhu@netcracker.com>

* feat(webhook): update leader election renew deadline and retry period durations

Signed-off-by: mosa0519 <mohanakrishna.sadhu@netcracker.com>

---------

Signed-off-by: mosa0519 <mohanakrishna.sadhu@netcracker.com>

* fix(webhook): validate ConfigMap references in SparkApplication specs (kubeflow#3117)

* fix(webhook): validate ConfigMap references in SparkApplication specs

An unusable or repeated ConfigMap reference is accepted today and only surfaces
in the pod the mutating webhook builds, which the API server then rejects over a
name the user never chose while the application itself looks healthy.
ScheduledSparkApplication carries the same spec, so it needs the same check.

Signed-off-by: rjgoyln <pt40419@gmail.com>

* fix(webhook): point ConfigMap validation errors at the name field

Each configMaps entry carries both a name and a mount path, so an error that
stops at the list index leaves the reader guessing which of the two is wrong.

Signed-off-by: rjgoyln <pt40419@gmail.com>

---------

Signed-off-by: rjgoyln <pt40419@gmail.com>

* docs: replace Enterprise Gateway notebooks guide with Kubeflow SDK (kubeflow#3141)

* docs: replace Enterprise Gateway notebooks guide with Kubeflow SDK

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* docs: correct Kubeflow SDK install to kubeflow[spark]

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* fixing unpushed commit

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* chore: untrack Python bytecode and ignore __pycache__

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

---------

Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>

* docs: add SparkConnect API reference to api-docs.md (kubeflow#3126)

* docs: generate api reference for SparkConnect (v1alpha1)

Add api/doc.go with 'package api' so gen-crd-api-reference-docs can
parse the parent api directory. Update Makefile build-api-docs target
to point at the parent api directory to capture both v1beta2 and
v1alpha1 packages.

Fixes kubeflow#3114

Signed-off-by: Fumer057 <fumer057@users.noreply.github.com>

* chore: trigger CI to resolve network flake

Signed-off-by: Fumer057 <fumer057@users.noreply.github.com>

* docs: address reviewer comments for SparkConnect API reference

Signed-off-by: Fumer057 <fumer057@users.noreply.github.com>

* docs: fix copyright header in api/doc.go

Signed-off-by: Fumer057 <fumer057@users.noreply.github.com>

---------

Signed-off-by: Fumer057 <fumer057@users.noreply.github.com>
Co-authored-by: Fumer057 <fumer057@users.noreply.github.com>

* fix(webhook): validate ConfigMap mount-path uniqueness (kubeflow#3140)

* fix(webhook): validate ConfigMap mount-path uniqueness

Motivation:
spec.{driver,executor}.configMaps entries with different names but the
same mount path pass admission today, since the ConfigMap validator only
rejected duplicate names. The mutating webhook then builds one volumeMount
per entry, so two entries sharing a path produce two volumeMounts with an
identical mountPath, which the API server rejects at pod creation with a
generic error that gives no hint the SparkApplication spec is the cause.

Conversely, mounting the same ConfigMap at two different paths was
rejected outright by the duplicate-name check, even though that is a
legitimate configuration: the mutating webhook just needs to build one
pod volume per distinct ConfigMap and one volumeMount per entry, instead
of one volume per entry.

Approach:
- internal/webhook/configmap_validator.go: validateConfigMapList now
  rejects entries whose mount Path repeats within the same driver or
  executor configMaps list, instead of rejecting a repeated Name.
- internal/webhook/sparkpod_defaulter.go: addGeneralConfigMaps now adds
  each derived ConfigMap volume at most once, while still emitting a
  volumeMount for every entry, so the same ConfigMap can be mounted at
  multiple paths without the pod ending up with two volumes sharing a
  name.

Validation:
- go test ./internal/webhook/... (including the ginkgo envtest suite,
  run with KUBEBUILDER_ASSETS pointed at the make setup-envtest binaries)
  passes, including new/updated cases: a duplicate mount path is now
  rejected, the same ConfigMap at two distinct paths is now accepted and
  produces one volume with two volumeMounts, and a previously-invalid
  "duplicate ConfigMap name" spec is now valid as long as paths differ.
- go test $(go list ./... | grep -v -e /drift -e /test/e2e) passes.
- make go-vet, make go-fmt (no diff), and make go-lint (0 issues) all
  pass clean; go mod tidy produced no changes.
- Branch is at upstream/master tip (ff244c1); upstream's own Integration
  Test workflow is green on recent master runs.

Report: kubeflow#3134
Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Assisted-by: claude-sonnet-5 (via Claude Code)

* fix(webhook): key ConfigMap volume dedup by source name, not generated volume name

Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>

* fix(webhook): reject empty and reserved ConfigMap mount paths

validateConfigMapList now rejects an empty configMaps[].path (which
otherwise builds an invalid VolumeMount) and a path that collides with
one the webhook reserves for the Spark, Hadoop, or Prometheus
ConfigMap it mounts itself, mirroring the same gating logic
addPrometheusConfig uses to decide whether that mount happens.

Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>

---------

Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Co-authored-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>

* docs: add SparkConnect to the website API reference (kubeflow#3138)

* docs: add SparkConnect to the website API reference

The reference section documented only the two v1beta2 kinds, so SparkConnect
(sparkoperator.k8s.io/v1alpha1) was missing from both the landing page card and
the API reference page.

make build-api-docs generates from api/v1beta2 only, so there is no generated
reference to link for SparkConnect. The page points at the Go types and the
Spark Connect user guide instead.

Signed-off-by: Shivansh Pandey <shivanshp987@gmail.com>

* docs: address review on the SparkConnect API reference

- Drop the version claim from the generated-reference link and the regeneration
  note. kubeflow#3126 extends build-api-docs to the parent api directory, so wording that
  named v1beta2 would be wrong once it lands; this phrasing holds either way.
- Remove the per-CRD facts table. It covered SparkConnect only, and hand-copying
  group, kind, resource and scope for all three would duplicate what
  docs/api-docs.md generates.
- Update the Reference card on the landing page (docs/website/index.rst) to name
  all three custom resources.

Signed-off-by: Shivansh Pandey <shivanshp987@gmail.com>

---------

Signed-off-by: Shivansh Pandey <shivanshp987@gmail.com>

* fix: stop adding SparkApplication owner reference to executor pods (kubeflow#3152)

* Stop adding SparkApplication owner reference to executor pods

Since kubeflow#2670 executor pods carried two owners: the driver pod
(controller=true, set by Spark) and the SparkApplication
(non-controller, added at submission time). Kubernetes garbage
collection only deletes a dependent once all owners are gone, so
deleting the driver pod (manual delete, spec.suspend=true, Kueue
preemption) left executor pods orphaned until the SparkApplication
itself was deleted.

Drop the extra owner reference so executors are cascade-deleted with
the driver pod. Deleting the SparkApplication still cascades through
the driver pod to the executors.

Issue: kubeflow#2961
Signed-off-by: KR Ravindra <42912207+KR-Ravindra@users.noreply.github.com>

* Assert executor pods are deleted when a SparkApplication is suspended

Extend the spark-pi suspend/resume e2e test: wait for an executor pod to
exist before suspending, then after the application reaches Suspended
assert that no driver or executor pods labelled with the application
name remain. With the extra SparkApplication owner reference on
executors this assertion fails because the executor pods survive the
driver pod deletion.

Signed-off-by: KR Ravindra <42912207+KR-Ravindra@users.noreply.github.com>

---------

Signed-off-by: KR Ravindra <42912207+KR-Ravindra@users.noreply.github.com>

---------

Signed-off-by: Jay Bharadwaj <jbharadw@openai.com>
Signed-off-by: Tamir Krasna <64045253+Tankdoggo@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Vikas Saxena <Vikas.Saxena.2006@gmail.com>
Signed-off-by: Vikas Saxena <90456542+vikas-saxena02@users.noreply.github.com>
Signed-off-by: dineshkumar181094 <dineshkumar181094@gmail.com>
Signed-off-by: immanuwell <pchpr.00@list.ru>
Signed-off-by: Vidyansh <ezboi2312@gmail.com>
Signed-off-by: rjgoyln <pt40419@gmail.com>
Signed-off-by: rohith1X8 <rohith18p18@gmail.com>
Signed-off-by: mosa0519 <mohanakrishna.sadhu@netcracker.com>
Signed-off-by: Fumer057 <fumer057@users.noreply.github.com>
Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Signed-off-by: Shivansh Pandey <shivanshp987@gmail.com>
Signed-off-by: KR Ravindra <42912207+KR-Ravindra@users.noreply.github.com>
Co-authored-by: Jay Bharadwaj <jayanth86@users.noreply.github.com>
Co-authored-by: Tamir Krasna <64045253+Tankdoggo@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vikas Saxena <90456542+vikas-saxena02@users.noreply.github.com>
Co-authored-by: Tariq Hasan <mmtariquehsn@gmail.com>
Co-authored-by: Dinesh Kumar <36758667+dineshkumar181094@users.noreply.github.com>
Co-authored-by: Immanuel Tikhonov <122638311+immanuwell@users.noreply.github.com>
Co-authored-by: Vidyansh Singh <141176362+reckless-sherixx@users.noreply.github.com>
Co-authored-by: rjgoyln <151457491+rjgoyln@users.noreply.github.com>
Co-authored-by: Rohith Peddi <rohith18p18@gmail.com>
Co-authored-by: Manabu McCloskey <manabu.mccloskey@gmail.com>
Co-authored-by: mohanakrishna sadhu <43145647+AxcelBlaze003@users.noreply.github.com>
Co-authored-by: Rishabh Shukla <rishabhshukla2901@gmail.com>
Co-authored-by: Fumer057 <fumer057@users.noreply.github.com>
Co-authored-by: Pujitha Paladugu <pujitha.rathna@gmail.com>
Co-authored-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Co-authored-by: shivansh <shivanshp987@gmail.com>
Co-authored-by: KR Ravindra <rajaravindra919@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants