Skip to content

feat(spark): add e2e test for s3a filesystem uri job submission (#595) - #905

Open
Neha-Nimsatkar wants to merge 23 commits into
kubeflow:mainfrom
Neha-Nimsatkar:feat/spark-s3a-e2e
Open

Neha-Nimsatkar wants to merge 23 commits into
kubeflow:mainfrom
Neha-Nimsatkar:feat/spark-s3a-e2e

Conversation

@Neha-Nimsatkar

@Neha-Nimsatkar Neha-Nimsatkar commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does / why we need it

Adds end-to-end integration and an example workflow notebook (examples/spark/batch_job_s3a.ipynb) demonstrating Spark batch job submissions with scripts hosted in S3-compatible object stores using the s3a:// URI scheme. It also introduces a modular, backend-agnostic object store testing framework for the E2E CI test suite.

Key Changes

  • Generic S3A Notebook:
    • Converted the legacy script into an interactive Jupyter notebook (examples/spark/batch_job_s3a.ipynb) parameterized via Papermill tags.
    • Replaced plaintext credential arguments with secure in-cluster Kubernetes secret bindings (spark.kubernetes.{role}.secretKeyRef.{key}).
    • Stripped redundant JVM/SSL properties, relying on standard Hadoop-AWS S3A resolution and modular AWS SDK v2 dependencies.
  • Pluggable Object Store E2E Architecture:
    • Decoupled generic object store orchestration (hack/e2e/object-store/setup.sh) from specific storage backends (hack/e2e/object-store/backends/seaweedfs.sh).
    • Added declarative S3 artifact seeding via hack/e2e/object-store/seed-job.yaml driven by E2E_S3_SEED.
    • Replaced hardcoded install_seaweedfs in hack/e2e-setup-cluster.sh with a configurable setup_object_store() hook gated on E2E_OBJECT_STORE.
  • CI Test Suite Integration:
    • Wired notebook execution into .github/workflows/test-spark-examples.yaml with dynamic PAPERMILL_PARAMS (S3_ENDPOINT, S3_REGION, S3_BUCKET, S3_PREFIX, S3_SECRET_NAME).
    • Configured cluster setup environment variables for SeaweedFS provisioning and test script seeding.
  • Documentation:
    • Updated docs/source/spark/batch-jobs.rst to reflect verified s3a:// remote script submission support and linked the example notebook.
    • Indexed the notebook in examples/spark/README.md.

Testing Done

  • Validated full local setup and notebook execution using hack/e2e-setup-cluster.sh and make test-e2e-notebook on Kind.
  • Verified Secret replication across namespaces and successful object seeding via seed-job.yaml.
  • Verified clean driver and executor pod lifecycle, secret mounting, and log retrieval.

Which issue(s) this PR fixes

Fixes #595

Checklist

  • Docs included if any changes are user facing
  • All PR review comments addressed and conversations resolved

@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 electronic-waste 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

@tariq-hasan

Copy link
Copy Markdown
Member

/ok-to-test

@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@Neha-Nimsatkar
Neha-Nimsatkar force-pushed the feat/spark-s3a-e2e branch 3 times, most recently from baa18d8 to 83e4c58 Compare October 30, 2026 09:56

@tariq-hasan tariq-hasan left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the PR @Neha-Nimsatkar!

The local:// and https:// schemes are already covered in other notebooks so s3a:// was the missing gap that this PR covers.

Let's also refactor the PR to use the following layout. That way we keep the SeaweedFS-specific implementation details decoupled from S3A and generic object store setup for E2E tests.

  • hack/e2e/object-store/setup.sh: Secret/ConfigMap per client namespace, buckets + seed Job, verify, write outputs
  • hack/e2e/object-store/seed-job.yaml: aws-cli Job that creates buckets, uploads and verifies
  • hack/e2e/object-store/backends/seaweedfs.sh: deploy, wait for ready, create the user, print the endpoint
  • hack/e2e/object-store/backends/seaweedfs.yaml: Deployment + Service seaweedfs on 8333

Let's also use the following as the input interface that are read as environment variables setup.sh:

  • E2E_OBJECT_STORE: which server to deploy (seaweedfs)
  • E2E_S3_NAMESPACE: namespace the storage server runs in, separate from any test namespace
  • E2E_S3_CLIENT_NAMESPACES: client namespaces that get the Secret and ConfigMap
  • E2E_S3_BUCKETS: buckets created through the S3 API
  • E2E_S3_SEED: Job artifact paths uploaded through the S3 API
  • E2E_S3_ACCESS_KEY / E2E_S3_SECRET_KEY: credentials for the single test user

You can set these outputs from hack/e2e/object-store/setup.sh:

  • Secret e2e-s3-credentials in each client namespace with keys AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
  • ConfigMap e2e-s3-config in each client namespace with AWS_ENDPOINT_URL and AWS_REGION
  • Env file (${E2E_S3_ENV_FILE:-.e2e/object-store.env}) containing S3_ENDPOINT, S3_REGION, S3_SECRET_NAME and S3_BUCKETS

I have added review comments inline on the PR aligning with the above design so we can get this completed.

Comment thread hack/manifests/seaweedfs/install.sh Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

hack/manifests/ suggests product manifests, but this is test infrastructure.

Please move it to hack/e2e/object-store/ and split it into a generic setup.sh (that is responsible for Secret/ConfigMap per client namespace, buckets and seeding through the S3 API, verification, and writing outputs) and backends/seaweedfs.sh (that deploys the server, waits for it and create the user).

The inline comments below say which part each piece moves to.

Comment thread hack/manifests/seaweedfs/install.sh Outdated

set -euo pipefail

TARGET_NAMESPACE="${1:-default}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Using a single argument here is likely to mix up the server's namespace with the test namespace.

Insead of using a single argument here as the client namespace that receives the secret and hardcoding the the server namespace as seaweedfs let's read the inputs from the interface instead: E2E_S3_NAMESPACE for the server and E2E_S3_CLIENT_NAMESPACES for the namespaces that get the Secret and ConfigMap.

Comment thread hack/manifests/seaweedfs/install.sh Outdated
Comment on lines +22 to +32
echo "[INFO] Deploying SeaweedFS S3 storage and KFP artifact secrets..."
kubectl apply -f "${MANIFEST_FILE}"

# Replicate KFP artifact secret to target namespace if different from default
if [[ "${TARGET_NAMESPACE}" != "default" ]]; then
kubectl create secret generic mlpipeline-minio-artifact \
--namespace="${TARGET_NAMESPACE}" \
--from-literal=accesskey="minio" \
--from-literal=secretkey="minio123" \
--dry-run=client -o yaml | kubectl apply -f -
fi

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This secret is not used anywhere.

Please have setup.sh generate credentials that are random per run unless E2E_S3_ACCESS_KEY and E2E_S3_SECRET_KEY are set.

Then in each client namespace create the neutral Secret e2e-s3-credentials with the keys AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY and also create the ConfigMap e2e-s3-config with AWS_ENDPOINT_URL and AWS_REGION.

for ns in ${E2E_S3_CLIENT_NAMESPACES}; do
  kubectl -n "$ns" create secret generic e2e-s3-credentials \
    --from-literal=AWS_ACCESS_KEY_ID="$AK" --from-literal=AWS_SECRET_ACCESS_KEY="$SK" \
    --dry-run=client -o yaml | kubectl apply -f -
  kubectl -n "$ns" create configmap e2e-s3-config \
    --from-literal=AWS_ENDPOINT_URL="$ENDPOINT" --from-literal=AWS_REGION=us-east-1 \
    --dry-run=client -o yaml | kubectl apply -f -
done

Comment thread hack/manifests/seaweedfs/install.sh Outdated
Comment on lines +34 to +43
echo "[INFO] Waiting for SeaweedFS deployment to roll out..."
kubectl rollout status deployment/seaweedfs -n seaweedfs --timeout=120s
kubectl wait --for=condition=ready pod -l app=seaweedfs -n seaweedfs --timeout=60s

# Obtain pod name
POD_NAME="$(kubectl get pods -n seaweedfs -l app=seaweedfs -o jsonpath='{.items[0].metadata.name}')"

echo "[INFO] Initializing default bucket 'mlpipeline'..."
kubectl exec -n seaweedfs "${POD_NAME}" -- /bin/sh -c '
printf "PUT /mlpipeline HTTP/1.1\r\nHost: 127.0.0.1:8333\r\nConnection: close\r\n\r\n" | nc 127.0.0.1 8333'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The bucket creation here relies on authentication being off. The unsigned PUT only works because SeaweedFS has no users configured, so it accepts anyone. Once authentication is on, it gets 403 and the bucket is silently never created.

The result is also not checked.

Let's split this into two:

  • Creating the user is server-specific and goes in backends/seaweedfs.sh, using SeaweedFS's admin shell and with retries until the server is ready:
  kubectl -n "$E2E_S3_NAMESPACE" exec deploy/seaweedfs -- sh -c "
    for i in \$(seq 1 30); do
      echo 's3.configure -user e2e -access_key $AK -secret_key $SK -actions Read,Write,List,Admin -apply' | weed shell && exit 0
      sleep 2
    done; exit 1"
  • Creating buckets is generic and belongs in the seed Job, using aws s3 mb. That way callers can ask for buckets the same way whichever server is behind it.

Comment thread hack/manifests/seaweedfs/install.sh Outdated
Comment on lines +45 to +65
echo "[INFO] Uploading test spark_job.py script into SeaweedFS..."
kubectl exec -i -n seaweedfs "${POD_NAME}" -- /bin/sh -c 'cat << "PYEOF" > /tmp/spark_job.py
import sys
from pyspark.sql import SparkSession

if __name__ == "__main__":
spark = SparkSession.builder.appName("SeaweedFSS3ATest").getOrCreate()
partitions = int(sys.argv[1]) if len(sys.argv) > 1 else 2
count = spark.sparkContext.parallelize(range(1, 100), partitions).count()
print(f"Successfully processed count: {count}")
spark.stop()
PYEOF
SIZE=$(wc -c < /tmp/spark_job.py | tr -d " ")
{
printf "PUT /mlpipeline/spark_job.py HTTP/1.1\r\n"
printf "Host: 127.0.0.1:8333\r\n"
printf "Content-Length: %s\r\n" "$SIZE"
printf "Connection: close\r\n\r\n"
cat /tmp/spark_job.py
} | nc 127.0.0.1 8333
'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead of having a second fixture let's reuse examples/spark/spark_job.py.

Also let's drive seeding from E2E_S3_SEED with a generic Job:

  • Build a ConfigMap from the listed repo files.
  • Run an amazon/aws-cli Job (pinned by digest) with credentials from e2e-s3-credentials.
  • The Job creates each bucket in E2E_S3_BUCKETS, uploads each entry, and checks each one with aws s3api head-object.
  • setup.sh runs kubectl wait --for=condition=complete job/e2e-s3-seed --timeout=120s and fails on error.

The Spark workflow then passes E2E_S3_SEED="e2e/spark/spark_job.py=examples/spark/spark_job.py". Nothing in the fixture names the file.

Comment on lines +69 to +70
" \"spark.driver.extraJavaOptions\": \"-Divy.home=/tmp/.ivy2\",\n",
" \"spark.executor.extraJavaOptions\": \"-Divy.home=/tmp/.ivy2\",\n",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please remove these settings and verify if the CI passes. If so let's remove them.

Comment on lines +75 to +76
" \"spark.hadoop.fs.s3a.connection.ssl.enabled\": \"false\",\n",
" \"spark.hadoop.fs.s3a.impl\": \"org.apache.hadoop.fs.s3a.S3AFileSystem\",\n",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please also remove these settings and verify if the CI passes. If so let's remove them.

Comment thread hack/e2e-setup-cluster.sh Outdated
Comment on lines +341 to +349
install_seaweedfs() {
phase_start "install_seaweedfs"
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

"${script_dir}/manifests/seaweedfs/install.sh" "${NAMESPACE:-default}"
phase_end "install_seaweedfs"
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's replace with the following. The goal is to make the setup configurable and extensible beyond seaweedfs.

setup_object_store() {
    [[ -n "${E2E_OBJECT_STORE:-}" ]] || return 0
    phase_start "setup_object_store"
    local script_dir
    script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    E2E_S3_CLIENT_NAMESPACES="${E2E_S3_CLIENT_NAMESPACES:-$NAMESPACE}" \
        "${script_dir}/e2e/object-store/setup.sh"
    phase_end "setup_object_store"
}

Comment on lines 84 to 86
SPARK_TEST_CLUSTER: spark-test
SPARK_TEST_NAMESPACE: spark-test
SPARK_OPERATOR_IMAGE_TAG: local

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's add the following as well to the list of environment variables.

E2E_OBJECT_STORE: seaweedfs
E2E_S3_SEED: "e2e/spark/spark_job.py=examples/spark/spark_job.py"

Comment on lines +113 to +117
make test-e2e-notebook \
NOTEBOOK_INPUT=./examples/spark/batch_job_s3a_seaweedfs.ipynb \
NOTEBOOK_OUTPUT=./artifacts/notebooks/batch_job_s3a_seaweedfs.ipynb \
PAPERMILL_TIMEOUT=600

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's add the configurations as well.

Suggested change
make test-e2e-notebook \
NOTEBOOK_INPUT=./examples/spark/batch_job_s3a_seaweedfs.ipynb \
NOTEBOOK_OUTPUT=./artifacts/notebooks/batch_job_s3a_seaweedfs.ipynb \
PAPERMILL_TIMEOUT=600
make test-e2e-notebook \
NOTEBOOK_INPUT=./examples/spark/batch_job_s3a.ipynb \
NOTEBOOK_OUTPUT=./artifacts/notebooks/batch_job_s3a.ipynb \
PAPERMILL_PARAMS="-p S3_ENDPOINT $S3_ENDPOINT -p S3_REGION $S3_REGION -p S3_BUCKET e2e -p S3_PREFIX spark -p S3_SECRET_NAME $S3_SECRET_NAME" \
PAPERMILL_TIMEOUT=600

@github-project-automation github-project-automation Bot moved this from Ready to Merge to Changes Requested in Kubeflow SDK Nov 2, 2026
@google-oss-prow google-oss-prow Bot added size/XL and removed size/L labels Nov 2, 2026
@Neha-Nimsatkar
Neha-Nimsatkar force-pushed the feat/spark-s3a-e2e branch 6 times, most recently from 91b92c6 to b1829cb Compare November 3, 2026 09:39
@tariq-hasan

Copy link
Copy Markdown
Member

/ok-to-test

@tariq-hasan

Copy link
Copy Markdown
Member

/ok-to-test
/retest

@Neha-Nimsatkar
Neha-Nimsatkar marked this pull request as ready for review November 3, 2026 10:49
Signed-off-by: Tariq Hasan <mmtariquehsn@gmail.com>
@tariq-hasan

Copy link
Copy Markdown
Member

Rebasing on master once #930 merges should help with this since it pins the latest commit, updates the Python models and also updates the E2E tests to more accurately reflect upstream operator behavior.

Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
- Replace monolithic aws-java-sdk-bundle with modular software.amazon.awssdk packages (s3, s3-transfer-manager)
- Exclude software.amazon.awssdk:bundle to reduce Ivy download footprint from ~550MB to ~14MB
- Resolve controller/driver timeouts and ensure compatibility with Hadoop 3.4.1

Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
…p manifests

Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
…dFS parity

Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
…dFS setup

Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
…grate with e2e test suite

Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
- decouple generic object-store setup from seaweedfs backend
- rename batch_job_s3a_seaweedfs.ipynb to batch_job_s3a.ipynb
- add papermill parameters and secure k8s secretKeyRef configuration
- drop unverified disclaimer from docs and update spark examples README
- update e2e setup cluster script and test workflow with seed job and params

Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
…ect store

- Decouple object store backend scripts and manifests under hack/e2e/object-store/
- Remove hardcoded credentials and switch to neutral e2e-s3-credentials secret
- Update batch_job_s3a.ipynb with tagged parameters cell and secretKeyRef configs
- Configure CI workflow with explicit S3 seed path and papermill parameters

Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
…arams

Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
Signed-off-by: Neha Nimsatkar <1492neha@gmail.com>
@Neha-Nimsatkar

Copy link
Copy Markdown
Contributor Author

Hi @tariq-hasan, I've rebased on top of #930 . Could you please run /ok-to-test so we can verify if the operator fix resolves the CI run?

@tariq-hasan

Copy link
Copy Markdown
Member

/ok-to-test

@tariq-hasan

Copy link
Copy Markdown
Member

/ok-to-test
/retest

@Neha-Nimsatkar

Copy link
Copy Markdown
Contributor Author

Hi @tariq-hasan, could you please click Approve and run on the workflow checks?

@tariq-hasan tariq-hasan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have added a few more comments.

spec:
containers:
- name: seaweedfs
image: chrislusf/seaweedfs:3.79@sha256:21425305e04b355904ef350ff75a4e81b8afc5124f63e0485b946164b58562d3

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's move to a current SeaweedFS release, pinned by digest.

Suggested change
image: chrislusf/seaweedfs:3.79@sha256:21425305e04b355904ef350ff75a4e81b8afc5124f63e0485b946164b58562d3
image: ghcr.io/chrislusf/seaweedfs:4.50@sha256:<digest>

Comment on lines +40 to +46
ports:
- containerPort: 8333
name: s3
- containerPort: 9333
name: master
- containerPort: 8080
name: volume

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add a readiness probe. Without one, kubectl rollout status in seaweedfs.sh returns as soon as the container starts, and the seed Job (with backoffLimit: 0) can hit 8333 before the gateway is listening. Please add the following after ports::

          readinessProbe:
            httpGet:
              path: /status
              port: 8333
            periodSeconds: 2
            failureThreshold: 30

Comment on lines +39 to +41
set -euo pipefail

echo "Creating buckets: ${E2E_S3_BUCKETS}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Even with the readiness probe, there's a window after s3.configure -apply where SeaweedFS hasn't loaded the identity yet and still accepts anonymous requests. Seeding (and later the Spark job) can then pass without credentials ever being checked.

Let's wait until signed requests work and anonymous requests are rejected. That covers readiness and also proves auth is enforced:

Suggested change
set -euo pipefail
echo "Creating buckets: ${E2E_S3_BUCKETS}"
set -euo pipefail
echo "Waiting for S3 API with authentication enforced..."
ready=0
for i in $(seq 1 30); do
if aws --endpoint-url="${AWS_ENDPOINT_URL}" s3api list-buckets >/dev/null 2>&1 \
&& ! aws --endpoint-url="${AWS_ENDPOINT_URL}" --no-sign-request s3api list-buckets >/dev/null 2>&1; then
ready=1; break
fi
sleep 2
done
[ "${ready}" = 1 ] || { echo "S3 API not ready or anonymous access is allowed" >&2; exit 1; }
echo "Creating buckets: ${E2E_S3_BUCKETS}"

optional: true
containers:
- name: aws-cli
image: amazon/aws-cli:2.15.15@sha256:76e05762337b3941d4af9c5e9dfb1cee3e8b88d615788ffd38be4e5a793570c7

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's bump amazon/aws-cli:2.15.15 as it is an older version.

Comment on lines +52 to +66
FIRST_BUCKET=$(echo "${E2E_S3_BUCKETS}" | awk '{print $1}')

if [ -n "${E2E_S3_SEED:-}" ]; then
for item in ${E2E_S3_SEED}; do
s3_key="${item%%=*}"
local_name="${item#*=}"
filename="$(basename "${local_name}")"

echo "Uploading /seed/${filename} to s3://${FIRST_BUCKET}/${s3_key}..."
aws --endpoint-url="${AWS_ENDPOINT_URL}" s3 cp "/seed/${filename}" "s3://${FIRST_BUCKET}/${s3_key}"

echo "Verifying s3://${FIRST_BUCKET}/${s3_key}..."
aws --endpoint-url="${AWS_ENDPOINT_URL}" s3api head-object --bucket "${FIRST_BUCKET}" --key "${s3_key}"
done
fi

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The original interface was E2E_S3_SEED="<bucket>/<key>=<repo path>", e.g. e2e/spark/spark_job.py=examples/spark/spark_job.py. Uploading everything to FIRST_BUCKET means callers can't seed a second bucket, and the target bucket is implied rather than stated.

Mounting by basename also collides when two entries share a filename (e.g. two spark_job.py fixtures). Let's key the ConfigMap by index; setup.sh iterates in the same order:

Suggested change
FIRST_BUCKET=$(echo "${E2E_S3_BUCKETS}" | awk '{print $1}')
if [ -n "${E2E_S3_SEED:-}" ]; then
for item in ${E2E_S3_SEED}; do
s3_key="${item%%=*}"
local_name="${item#*=}"
filename="$(basename "${local_name}")"
echo "Uploading /seed/${filename} to s3://${FIRST_BUCKET}/${s3_key}..."
aws --endpoint-url="${AWS_ENDPOINT_URL}" s3 cp "/seed/${filename}" "s3://${FIRST_BUCKET}/${s3_key}"
echo "Verifying s3://${FIRST_BUCKET}/${s3_key}..."
aws --endpoint-url="${AWS_ENDPOINT_URL}" s3api head-object --bucket "${FIRST_BUCKET}" --key "${s3_key}"
done
fi
if [ -n "${E2E_S3_SEED:-}" ]; then
i=0
for item in ${E2E_S3_SEED}; do
dest="${item%%=*}"
case "${dest}" in */*) ;; *) echo "E2E_S3_SEED entry '${item}' must be <bucket>/<key>=<path>" >&2; exit 1 ;; esac
bucket="${dest%%/*}"
key="${dest#*/}"
echo "Uploading seed-${i} to s3://${bucket}/${key}..."
aws --endpoint-url="${AWS_ENDPOINT_URL}" s3 cp "/seed/seed-${i}" "s3://${bucket}/${key}"
aws --endpoint-url="${AWS_ENDPOINT_URL}" s3api head-object --bucket "${bucket}" --key "${key}"
i=$((i + 1))
done
fi

envsubst '${E2E_S3_BUCKETS} ${E2E_S3_SEED}' < "${SCRIPT_DIR}/seed-job.yaml" | kubectl apply -n "${PRIMARY_CLIENT_NS}" -f -

echo "Waiting for seed Job to complete..."
kubectl wait --for=condition=complete job/e2e-s3-seed -n "${PRIMARY_CLIENT_NS}" --timeout=120s || (kubectl logs -n "${PRIMARY_CLIENT_NS}" -l job-name=e2e-s3-seed --tail=50 && kubectl describe job/e2e-s3-seed -n "${PRIMARY_CLIENT_NS}" && exit 1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since backoffLimit: 0, a failed seed Job never becomes complete, so this waits the full 120 s before printing logs. Let's stop on either outcome:

Suggested change
kubectl wait --for=condition=complete job/e2e-s3-seed -n "${PRIMARY_CLIENT_NS}" --timeout=120s || (kubectl logs -n "${PRIMARY_CLIENT_NS}" -l job-name=e2e-s3-seed --tail=50 && kubectl describe job/e2e-s3-seed -n "${PRIMARY_CLIENT_NS}" && exit 1)
for _ in $(seq 1 60); do
succeeded="$(kubectl get job/e2e-s3-seed -n "${PRIMARY_CLIENT_NS}" -o jsonpath='{.status.succeeded}')"
failed="$(kubectl get job/e2e-s3-seed -n "${PRIMARY_CLIENT_NS}" -o jsonpath='{.status.failed}')"
[[ "${succeeded:-0}" -ge 1 ]] && break
if [[ "${failed:-0}" -ge 1 || "${_}" -eq 60 ]]; then
kubectl logs -n "${PRIMARY_CLIENT_NS}" -l job-name=e2e-s3-seed --tail=100 || true
kubectl describe job/e2e-s3-seed -n "${PRIMARY_CLIENT_NS}" || true
exit 1
fi
sleep 2
done

Comment on lines +87 to +89
E2E_OBJECT_STORE: seaweedfs
E2E_S3_BUCKETS: e2e
E2E_S3_SEED: "spark/spark_job.py=examples/spark/spark_job.py"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With the <bucket>/<key> seed format:

Suggested change
E2E_OBJECT_STORE: seaweedfs
E2E_S3_BUCKETS: e2e
E2E_S3_SEED: "spark/spark_job.py=examples/spark/spark_job.py"
E2E_OBJECT_STORE: seaweedfs
E2E_S3_BUCKETS: e2e
E2E_S3_SEED: "e2e/spark/spark_job.py=examples/spark/spark_job.py"

"job_name = client.submit_job(\n",
" job=FileJob(\n",
" file_source=file_uri,\n",
" args=[\"10\"],\n",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

spark_job.py doesn't read sys.argv, so args=["10"] is a leftover from the earlier fixture. Let's drop it.

"source": [
"print(f\"Driver pod logs for {job_name}:\")\n",
"print(\"-\" * 70)\n",
"for line in client.get_job_logs(job_name):\n",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The test currently passes on status alone. Let's assert that the driver actually ran our script from S3:

logs = list(client.get_job_logs(job_name))
for line in logs:
    print(line.rstrip())
assert any("Row count: 10" in line for line in logs), "Expected output from spark_job.py not found in driver logs"

Comment thread examples/spark/README.md
Comment on lines +23 to 29
- **batch_job_s3a.ipynb** - Submit a `FileJob` referencing an application script stored in S3 (`s3a://` URI)
- **batch_job_lifecycle.ipynb** - Submit a `FileJob` and exercise the batch job lifecycle (`get_job`, `list_jobs`, `get_job_logs`, `delete_job`)
- **batch_func_job_lifecycle.ipynb** - Submit a `FuncJob` and exercise the batch job lifecycle
- **batch_failed_job.ipynb** - Submit a `FileJob` expected to fail and inspect the `FAILED` state
- **batch_job_options.ipynb** - Submit a batch job with Kubernetes options (labels, annotations, node selector, tolerations, custom name)
See the [Options Reference](https://sdk.kubeflow.org/en/latest/spark/options.html) for details on Kubernetes options.
- **spark_job.py** - A simple Spark application used as the remote `file_source` for the batch job examples

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
- **batch_job_lifecycle.ipynb** - Submit a `FileJob` and exercise the batch job lifecycle (`get_job`, `list_jobs`, `get_job_logs`, `delete_job`)
- **batch_func_job_lifecycle.ipynb** - Submit a `FuncJob` and exercise the batch job lifecycle
- **batch_failed_job.ipynb** - Submit a `FileJob` expected to fail and inspect the `FAILED` state
- **batch_job_options.ipynb** - Submit a batch job with Kubernetes options (labels, annotations, node selector, tolerations, custom name)
- **batch_job_s3a.ipynb** - Submit a `FileJob` referencing an application script stored in S3 (`s3a://` URI)

@RobuRishabh

Copy link
Copy Markdown

/ok-to-test

This branch has not been deployed

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

Projects

Status: Changes Requested

Development

Successfully merging this pull request may close these issues.

KEP-107: Add E2E tests for Spark job submission using filesystem URIs

3 participants