feat: Add preinstalled mode to e2e test suite - #3045
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
72aff1a to
e61e5fc
Compare
There was a problem hiding this comment.
Pull request overview
Adds an e2e mode for testing clusters with an existing Spark operator deployment.
Changes:
- Parses
PREINSTALLEDto 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.
| 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. |
| export DEPLOY_METHOD="$(DEPLOY_METHOD)" && \ | ||
| export PREINSTALLED="$(PREINSTALLED)" && \ | ||
| export IMAGE_TAG="$(IMAGE_TAG)" && \ | ||
| export KUBECONFIG="$(KIND_KUBE_CONFIG)" && \ |
| 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") |
| installViaHelm() | ||
| case "kustomize": | ||
| installViaKustomize() | ||
| if !preinstalled { |
cdfa7ba to
ec2675e
Compare
| // 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)) | ||
| } |
| for _, container := range deployment.Spec.Template.Spec.Containers { | ||
| for _, arg := range container.Args { | ||
| if arg == "--enable-driver-pdb=true" { | ||
| driverPDBEnabled = true | ||
| break | ||
| } | ||
| } | ||
| } |
ec2675e to
4e125ab
Compare
| if !driverPDBEnabled { | ||
| Skip("Driver PDB feature not enabled in operator (--enable-driver-pdb=true required)") |
| if err == nil { | ||
| By("Spark ServiceAccount already exists, skipping RBAC application") | ||
| // RBAC is already configured by the preinstalled operator | ||
| } else if apierrors.IsNotFound(err) { |
| rbacDelCmd := exec.Command("kubectl", "delete", "-k", sparkRBACDir, "-n", "default", "--ignore-not-found", "--timeout=60s") | ||
| rbacDelCmd.Stdout = GinkgoWriter | ||
| rbacDelCmd.Stderr = GinkgoWriter | ||
| _ = rbacDelCmd.Run() |
| installViaHelm() | ||
| case "kustomize": | ||
| installViaKustomize() | ||
| if !preinstalled { |
| 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") |
| .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 |
4e125ab to
9646ac9
Compare
| if strings.HasPrefix(arg, "--job-namespace-selector=") { | ||
| jobNamespaceSelectorConfigured = true | ||
| } |
| 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>
9646ac9 to
dfe8571
Compare
| 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)) | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
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.
| if !jobNamespaceSelectorConfigured { | ||
| Skip("Namespace filtering test requires operator configured with --job-namespace-selector flag") |
There was a problem hiding this comment.
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
| switch deployMethod { | ||
| case "helm": | ||
| mutatingWebhookName = "spark-operator-webhook" | ||
| validatingWebhookName = "spark-operator-webhook" |
There was a problem hiding this comment.
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.
| 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", |
There was a problem hiding this comment.
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"
}
| 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()) |
There was a problem hiding this comment.
| 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)" && \ |
There was a problem hiding this comment.
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.
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=trueto skip operator install/uninstall--enable-driver-pdb)--namespace-selector=)Environment Variables
PREINSTALLEDtrue,falsefalseDEPLOY_METHODhelm,kustomizehelmOPERATOR_NAMESPACEspark-operatorOPERATOR_DEPLOYMENT_NAMEspark-operator-controllerTest Behavior
Feature detection:
--enable-driver-pdbnot detected--namespace-selectornot configuredRBAC requirements:
Preinstalled mode requires complete Spark job RBAC in the
defaultnamespace:spark-operator-sparkspark-operator-spark(Helm) orspark-role(Kustomize)spark-operator-spark(Helm) orspark-role-binding(Kustomize)Usage
Preinstalled Mode (Managed Clusters)
Standard Mode (Unchanged)
Prerequisites for Preinstalled Mode
defaultnamespace:Benefits
Backward Compatibility
All existing workflows continue to work unchanged:
make e2e-teststill manages full operator lifecycleDEPLOY_METHOD=kustomize make e2e-teststill worksChange Category
Checklist