Skip to content

feat: Add preinstalled mode to e2e test suite - #3045

Open
shruthis4 wants to merge 1 commit into
kubeflow:masterfrom
shruthis4:feat/e2e-preinstalled-mode
Open

feat: Add preinstalled mode to e2e test suite#3045
shruthis4 wants to merge 1 commit into
kubeflow:masterfrom
shruthis4:feat/e2e-preinstalled-mode

Conversation

@shruthis4

@shruthis4 shruthis4 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Enable e2e tests to run against clusters where the Spark operator is already deployed, without requiring tests to manage operator installation and teardown.

This supports testing workflows on managed Kubernetes clusters (EKS, GKE, RHOAI) where operators are pre-deployed and long-lived.

Changes

Core Features

  • PREINSTALLED mode: Set PREINSTALLED=true to skip operator install/uninstall
  • Runtime feature detection: Test suite detects enabled features by inspecting deployment args
    • Driver PodDisruptionBudget support (--enable-driver-pdb)
    • Namespace selector configuration (--namespace-selector=)
  • RBAC validation: Verify Spark job RBAC exists before running tests
  • Configurable deployment location: Override namespace and deployment name for non-standard installations

Environment Variables

Variable Values Default Description
PREINSTALLED true, false false Skip operator install/uninstall when true
DEPLOY_METHOD helm, kustomize helm Determines webhook names and RBAC scheme
OPERATOR_NAMESPACE any namespace spark-operator Where to find the operator deployment
OPERATOR_DEPLOYMENT_NAME deployment name spark-operator-controller Controller deployment name

Test Behavior

Feature detection:

  • Tests requiring driver PDB skip when --enable-driver-pdb not detected
  • Tests requiring namespace filtering skip when --namespace-selector not configured
  • Suite fails early with clear instructions if Spark job RBAC is missing

RBAC requirements:
Preinstalled mode requires complete Spark job RBAC in the default namespace:

  • ServiceAccount: spark-operator-spark
  • Role: spark-operator-spark (Helm) or spark-role (Kustomize)
  • RoleBinding: spark-operator-spark (Helm) or spark-role-binding (Kustomize)

Usage

Preinstalled Mode (Managed Clusters)

# Operator installed via Helm
DEPLOY_METHOD=helm PREINSTALLED=true make e2e-test-preinstalled

# Operator installed via Kustomize
DEPLOY_METHOD=kustomize PREINSTALLED=true make e2e-test-preinstalled

# Custom deployment location
OPERATOR_NAMESPACE=my-namespace \
OPERATOR_DEPLOYMENT_NAME=my-spark-controller \
DEPLOY_METHOD=helm \
PREINSTALLED=true \
make e2e-test-preinstalled

Standard Mode (Unchanged)

# Default: Helm deployment, manages full lifecycle
make e2e-test

# Kustomize deployment
DEPLOY_METHOD=kustomize make e2e-test

Prerequisites for Preinstalled Mode

  1. Operator deployed and running in target cluster
  2. Spark job RBAC exists in default namespace:
    # Apply if missing
    kubectl apply -k config/spark-rbac -n default
    
    # Or ensure Helm chart has spark.rbac.create=true (default)
  3. KUBECONFIG points to target cluster
  4. DEPLOY_METHOD matches how operator was deployed

Benefits

  • Faster iteration: Deploy operator once, iterate on tests without reinstalling
  • CI/CD flexibility: Separate operator deployment from test execution
  • Pre-production testing: Validate against long-running operator instances
  • Managed cluster support: Test on EKS, GKE, AKS, RHOAI where operators are pre-deployed

Backward Compatibility

All existing workflows continue to work unchanged:

  • make e2e-test still manages full operator lifecycle
  • DEPLOY_METHOD=kustomize make e2e-test still works
  • Default behavior is non-preinstalled mode

Change Category

  • Feature (non-breaking change which adds functionality)

Checklist

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

Copilot AI review requested due to automatic review settings July 15, 2026 13:32
@google-oss-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign chenyi015 for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found 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

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.

Pull request overview

Adds an e2e mode for testing clusters with an existing Spark operator deployment.

Changes:

  • Parses PREINSTALLED to skip operator lifecycle management.
  • Configures webhook names by deployment method.
  • Applies Spark application RBAC during preinstalled runs.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
test/e2e/suite_test.go Implements preinstalled-mode setup and teardown behavior.
Makefile Passes preinstalled configuration to the e2e suite.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Makefile Outdated
e2e-test: envtest kind-load-image kind-load-spark-image ## Run the e2e tests against a Kind k8s instance that is spun up.
@echo "Running e2e tests (deploy_method=$(DEPLOY_METHOD))..."
DEPLOY_METHOD=$(DEPLOY_METHOD) IMAGE_TAG=$(IMAGE_TAG) KUBECONFIG=$(KIND_KUBE_CONFIG) go test ./test/e2e/ -v -ginkgo.v -timeout 30m
e2e-test: envtest kind-load-image kind-load-spark-image ## Run the e2e tests against a Kind k8s instance. DEPLOY_METHOD: helm (default) or kustomize. PREINSTALLED=true to skip install.
Comment thread Makefile
export DEPLOY_METHOD="$(DEPLOY_METHOD)" && \
export PREINSTALLED="$(PREINSTALLED)" && \
export IMAGE_TAG="$(IMAGE_TAG)" && \
export KUBECONFIG="$(KIND_KUBE_CONFIG)" && \
Comment thread test/e2e/suite_test.go Outdated
By("Applying Spark job RBAC to default namespace (preinstalled mode)")
repoRoot := filepath.Join("..", "..")
sparkRBACDir := filepath.Join(repoRoot, "config", "spark-rbac")
rbacCmd := exec.Command("kubectl", "apply", "-k", sparkRBACDir, "-n", "default", "--server-side", "--force-conflicts")
Comment thread test/e2e/suite_test.go
installViaHelm()
case "kustomize":
installViaKustomize()
if !preinstalled {
@shruthis4
shruthis4 force-pushed the feat/e2e-preinstalled-mode branch 2 times, most recently from cdfa7ba to ec2675e Compare July 15, 2026 15:20
@google-oss-prow google-oss-prow Bot added size/L and removed size/M labels Jul 15, 2026
@shruthis4
shruthis4 requested a review from Copilot July 15, 2026 15:21

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment thread test/e2e/suite_test.go Outdated
Comment on lines +173 to +193
// Check if Spark RBAC already exists (may have been created by Helm or Kustomize)
By("Checking if Spark RBAC already exists")
sa := &corev1.ServiceAccount{}
err = k8sClient.Get(context.TODO(),
types.NamespacedName{Name: "spark-operator-spark", Namespace: "default"}, sa)

if err == nil {
By("Spark ServiceAccount already exists, skipping RBAC application")
// RBAC is already configured by the preinstalled operator
} else if errors.IsNotFound(err) {
By("Spark ServiceAccount not found, applying RBAC to default namespace")
repoRoot := filepath.Join("..", "..")
sparkRBACDir := filepath.Join(repoRoot, "config", "spark-rbac")
rbacCmd := exec.Command("kubectl", "apply", "-k", sparkRBACDir, "-n", "default")
rbacCmd.Stdout = GinkgoWriter
rbacCmd.Stderr = GinkgoWriter
Expect(rbacCmd.Run()).NotTo(HaveOccurred(), "Failed to apply Spark job RBAC")
sparkRBACApplied = true
} else {
Fail(fmt.Sprintf("Failed to check for Spark ServiceAccount: %v", err))
}
Comment thread test/e2e/suite_test.go
Comment on lines +204 to +211
for _, container := range deployment.Spec.Template.Spec.Containers {
for _, arg := range container.Args {
if arg == "--enable-driver-pdb=true" {
driverPDBEnabled = true
break
}
}
}

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

Comment thread test/e2e/pdb_test.go
Comment on lines +67 to +68
if !driverPDBEnabled {
Skip("Driver PDB feature not enabled in operator (--enable-driver-pdb=true required)")
Comment thread test/e2e/suite_test.go Outdated
Comment on lines +180 to +183
if err == nil {
By("Spark ServiceAccount already exists, skipping RBAC application")
// RBAC is already configured by the preinstalled operator
} else if apierrors.IsNotFound(err) {
Comment thread test/e2e/suite_test.go Outdated
rbacDelCmd := exec.Command("kubectl", "delete", "-k", sparkRBACDir, "-n", "default", "--ignore-not-found", "--timeout=60s")
rbacDelCmd.Stdout = GinkgoWriter
rbacDelCmd.Stderr = GinkgoWriter
_ = rbacDelCmd.Run()
Comment thread test/e2e/suite_test.go
installViaHelm()
case "kustomize":
installViaKustomize()
if !preinstalled {
Comment thread test/e2e/suite_test.go Outdated
Comment on lines +199 to +203
deployment := &appsv1.Deployment{}
err = k8sClient.Get(context.TODO(),
types.NamespacedName{Name: "spark-operator-controller", Namespace: ReleaseNamespace},
deployment)
Expect(err).NotTo(HaveOccurred(), "Failed to get controller deployment")
Comment thread Makefile
Comment on lines +208 to +213
.PHONY: e2e-test-preinstalled
e2e-test-preinstalled: envtest ## Run the e2e tests against a preinstalled operator. DEPLOY_METHOD: helm (default) or kustomize.
@echo "Running e2e tests against preinstalled operator (deploy_method=$(DEPLOY_METHOD))..."
export DEPLOY_METHOD="$(DEPLOY_METHOD)" && \
export PREINSTALLED="true" && \
go test ./test/e2e/ -v -ginkgo.v -timeout 30m

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread test/e2e/suite_test.go Outdated
Comment on lines +251 to +253
if strings.HasPrefix(arg, "--job-namespace-selector=") {
jobNamespaceSelectorConfigured = true
}
Comment thread test/e2e/suite_test.go Outdated
By("Cleaning up test-created Spark RBAC from default namespace")
repoRoot := filepath.Join("..", "..")
sparkRBACDir := filepath.Join(repoRoot, "config", "spark-rbac")
rbacDelCmd := exec.Command("kubectl", "delete", "-k", sparkRBACDir, "-n", "default", "--ignore-not-found", "--timeout=60s")
Enable e2e tests to run against clusters where the Spark operator is
already deployed (EKS, GKE, RHOAI managed clusters).

- Add PREINSTALLED env var to skip operator install/uninstall
- Add OPERATOR_NAMESPACE and OPERATOR_DEPLOYMENT_NAME env vars for custom deployments
- Detect driver PDB and namespace selector features at runtime from deployment args
- Validate Spark RBAC exists based on deployment method (Helm vs Kustomize)
- Skip feature-specific tests when features unavailable
- Prevent nil panic in pdb_test.go AfterEach when test skipped

Environment variables:
- PREINSTALLED: Set to 'true' to skip operator lifecycle management
- DEPLOY_METHOD: 'helm' or 'kustomize' (default: helm) - determines webhook and RBAC naming
- OPERATOR_NAMESPACE: Namespace where operator is deployed (default: spark-operator)
- OPERATOR_DEPLOYMENT_NAME: Controller deployment name (default: spark-operator-controller)

Requirements for preinstalled mode:
- Operator must be deployed and running
- Spark job RBAC must exist in 'default' namespace (ServiceAccount, Role, RoleBinding)
- Set DEPLOY_METHOD to match how operator was deployed

Signed-off-by: Shruthi Sankepelly <shruthi.s4@gmail.com>
@shruthis4
shruthis4 force-pushed the feat/e2e-preinstalled-mode branch from 9646ac9 to dfe8571 Compare July 15, 2026 17:05
Comment thread test/e2e/suite_test.go
Comment on lines +208 to 237
allMissing := true
sa := &corev1.ServiceAccount{}
if err := k8sClient.Get(context.TODO(),
types.NamespacedName{Name: "spark-operator-spark", Namespace: "default"}, sa); err == nil {
allMissing = false // SA exists
}

role := &rbacv1.Role{}
if err := k8sClient.Get(context.TODO(),
types.NamespacedName{Name: roleName, Namespace: "default"}, role); err == nil {
allMissing = false // Role exists
}

roleBinding := &rbacv1.RoleBinding{}
if err := k8sClient.Get(context.TODO(),
types.NamespacedName{Name: roleBindingName, Namespace: "default"}, roleBinding); err == nil {
allMissing = false // RoleBinding exists
}

if allMissing {
Fail(fmt.Sprintf("Spark job RBAC is missing in preinstalled mode.\n"+
"The e2e suite requires ServiceAccount, Role, and RoleBinding for Spark applications in the 'default' namespace.\n"+
"Please apply RBAC before running tests:\n"+
" kubectl apply -k config/spark-rbac -n default\n"+
"Or if using Helm, ensure spark.rbac.create=true (default)"))
} else {
By(fmt.Sprintf("Spark RBAC exists for %s deployment", deployMethod))
}
}

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.

The allMissing predicate is inverted relative to the comment (“ALL are missing”). Setting allMissing = false when any of SA/Role/RoleBinding exists means a lone ServiceAccount satisfies the check. Helm can create SA without Role/RoleBinding (spark.serviceAccount.create vs spark.rbac.create). Please require all three to exist (and surface unexpected Get errors), e.g. fail unless saOK && roleOK && roleBindingOK.

Comment on lines +147 to +148
if !jobNamespaceSelectorConfigured {
Skip("Namespace filtering test requires operator configured with --job-namespace-selector flag")

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.

Nit: Skip text says --job-namespace-selector, but the controller flag is --namespace-selector (cmd/operator/controller/start.go). need to align the message with the real flag

Comment thread test/e2e/suite_test.go
switch deployMethod {
case "helm":
mutatingWebhookName = "spark-operator-webhook"
validatingWebhookName = "spark-operator-webhook"

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.

Webhook names are still hardcoded to spark-operator-webhook for Helm. The Helm chart derives these from {{ fullname }}-webhook, which changes with fullnameOverride or a non-default release name. So a preinstalled operator with fullnameOverride: my-operator will time out at the "Waiting for webhooks" step even when OPERATOR_NAMESPACE and OPERATOR_DEPLOYMENT_NAME are correctly set.

Worth either adding MUTATING_WEBHOOK_NAME/VALIDATING_WEBHOOK_NAME overrides (consistent with the other OPERATOR_* vars), or documenting that preinstalled Helm mode currently requires the default release name.

Comment thread test/e2e/suite_test.go
types.NamespacedName{Name: operatorDeploymentName, Namespace: operatorNamespace},
deployment)
if err != nil {
Fail(fmt.Sprintf("Failed to get controller deployment %s/%s: %v\nIn preinstalled mode, set OPERATOR_NAMESPACE and OPERATOR_DEPLOYMENT_NAME to match your deployment",

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.

The feature detection runs unconditionally (both preinstalled and non-preinstalled), but the Fail() message only mentions preinstalled mode. If this fails in standard mode (e.g., the Helm install partially failed), the error message will mislead. Consider branching the message:

hint := "Check that the operator deployment completed successfully"
if preinstalled {
    hint = "In preinstalled mode, set OPERATOR_NAMESPACE and OPERATOR_DEPLOYMENT_NAME to match your deployment"
}

Comment thread test/e2e/suite_test.go
Comment on lines 262 to 266
By("Waiting for the webhooks to be ready")
mutatingWebhookKey := types.NamespacedName{Name: mutatingWebhookName}
validatingWebhookKey := types.NamespacedName{Name: validatingWebhookName}
Expect(waitForMutatingWebhookReady(context.Background(), mutatingWebhookKey)).NotTo(HaveOccurred())
Expect(waitForValidatingWebhookReady(context.Background(), validatingWebhookKey)).NotTo(HaveOccurred())

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.

Comment thread Makefile
Comment on lines +201 to +205
e2e-test: envtest kind-load-image kind-load-spark-image ## Run the e2e tests against a Kind k8s instance. DEPLOY_METHOD: helm (default) or kustomize.
@echo "Running e2e tests (deploy_method=$(DEPLOY_METHOD))..."
DEPLOY_METHOD=$(DEPLOY_METHOD) IMAGE_TAG=$(IMAGE_TAG) KUBECONFIG=$(KIND_KUBE_CONFIG) go test ./test/e2e/ -v -ginkgo.v -timeout 30m
export DEPLOY_METHOD="$(DEPLOY_METHOD)" && \
export IMAGE_TAG="$(IMAGE_TAG)" && \
export KUBECONFIG="$(KIND_KUBE_CONFIG)" && \

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.

The existing e2e-test target was changed from inline env-var style (VAR=val cmd) to export VAR && cmd. Both work, but the original inline form is more idiomatic for Makefiles and scopes the vars to just the go test process. This change isn't needed for the PR and adds noise to the diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants