Repository navigation
Add workload through leveraging OCI images to run on CI #438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| #!/usr/bin/env bash | ||
| # Helpers shared by the OCI CI lanes. | ||
| set -euo pipefail | ||
|
|
||
| fail() | ||
| { | ||
| echo "FAIL: $*" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| # Drain stdin so pipefail does not report the producer's SIGPIPE. | ||
| expect_grep() | ||
| { | ||
| grep -F -- "$1" > /dev/null | ||
| } | ||
|
|
||
| run_capture() | ||
| { | ||
| local desc="$1" pattern="$2" out | ||
| shift 2 | ||
| out="$("$@")" || fail "$desc: exited $?: $out" | ||
| printf '%s: %s\n' "$desc" "$out" | ||
| printf '%s\n' "$out" | expect_grep "$pattern" || fail "$desc: output lacks $pattern" | ||
| } | ||
|
|
||
| # Poll a predicate without letting its failures escape set -e. | ||
| wait_for() | ||
| { | ||
| local timeout="$1" desc="$2" i=0 | ||
| shift 2 | ||
| while ! "$@" > /dev/null; do | ||
| i=$((i + 1)) | ||
| [ "$i" -le $((timeout * 2)) ] || fail "timed out after ${timeout}s waiting for: $desc" | ||
| sleep 0.5 | ||
| done | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,165 @@ | ||||||||
| #!/usr/bin/env bash | ||||||||
| # Run one elfuse runtime lane on an OCI image and require its sentinel. The keys | ||||||||
| # are the case arms below and the oci-runtime matrix in build.yml. Usage: | ||||||||
| # ELFUSE_OCI_STORE=<store> scripts/ci/oci-runtime.sh <key> | ||||||||
| # shellcheck source=scripts/ci/oci-lib.sh | ||||||||
| . "$(dirname "$0")/oci-lib.sh" | ||||||||
|
|
||||||||
| ROOT="$(cd "$(dirname "$0")/../.." && pwd)" | ||||||||
| BIN="$ROOT/scripts/elfuse-oci.py" | ||||||||
| : "${ELFUSE_OCI_STORE:?set ELFUSE_OCI_STORE to the store directory to use}" | ||||||||
| export ELFUSE_OCI_STORE | ||||||||
|
|
||||||||
| # The library images through mirror.gcr.io, so the lane does not depend on | ||||||||
| # Docker Hub's anonymous pull limit. | ||||||||
| MIRROR=mirror.gcr.io/library | ||||||||
|
|
||||||||
| key="${1:?usage: oci-runtime.sh <key>}" | ||||||||
| WL="$ROOT/scripts/ci/workloads" | ||||||||
|
|
||||||||
| guest="" | ||||||||
| srv_outfile="" | ||||||||
| on_exit() | ||||||||
| { | ||||||||
| rc=$? | ||||||||
| # Reap the guest before a persistent runner reuses its port or clone. | ||||||||
| if [ -n "$guest" ] && kill -0 "$guest" 2> /dev/null; then | ||||||||
| kill "$guest" 2> /dev/null || true | ||||||||
| wait "$guest" 2> /dev/null || true | ||||||||
| fi | ||||||||
| if [ "$rc" -ne 0 ] && [ -s "$srv_outfile" ]; then | ||||||||
| echo "--- server guest output ---" >&2 | ||||||||
| cat "$srv_outfile" >&2 | ||||||||
| fi | ||||||||
| [ -z "$srv_outfile" ] || rm -f "$srv_outfile" | ||||||||
| exit "$rc" | ||||||||
| } | ||||||||
| trap on_exit EXIT | ||||||||
|
|
||||||||
| server_printed() | ||||||||
| { | ||||||||
| kill -0 "$guest" 2> /dev/null || fail "server guest exited before readiness" | ||||||||
| grep -Eq "$1" "$srv_outfile" | ||||||||
| } | ||||||||
|
|
||||||||
| server_gone() | ||||||||
| { | ||||||||
| ! kill -0 "$guest" 2> /dev/null | ||||||||
| } | ||||||||
|
|
||||||||
| # Start a server guest in the background and wait for pattern in its output. | ||||||||
| start_server() | ||||||||
| { | ||||||||
| local pattern="$1" | ||||||||
| shift | ||||||||
| srv_outfile="$(mktemp)" | ||||||||
| "$@" > "$srv_outfile" 2>&1 & | ||||||||
| guest=$! | ||||||||
| wait_for 60 "server output matching $pattern" server_printed "$pattern" | ||||||||
| } | ||||||||
|
|
||||||||
| await_server_exit() | ||||||||
| { | ||||||||
| wait_for 10 "$1 exit" server_gone | ||||||||
| wait "$guest" || { | ||||||||
| guest="" | ||||||||
| fail "$1 exited non-zero" | ||||||||
| } | ||||||||
| guest="" | ||||||||
| } | ||||||||
|
|
||||||||
| run_node() | ||||||||
| { | ||||||||
| run_capture node-compute elfuse-oci-node-compute-ok \ | ||||||||
| "$BIN" run --entrypoint /usr/local/bin/node "$MIRROR/node:22-alpine" \ | ||||||||
| -e "$(cat "$WL/node-compute.js")" | ||||||||
|
|
||||||||
| start_server '^PORT=' \ | ||||||||
| "$BIN" run --entrypoint /usr/local/bin/node "$MIRROR/node:22-alpine" \ | ||||||||
| -e "$(cat "$WL/node-server.js")" | ||||||||
| local port | ||||||||
| port="$(awk -F= '/^PORT=/{print $2; exit}' "$srv_outfile")" | ||||||||
| # Flushed output can precede listen readiness. | ||||||||
| wait_for 30 "node server on 127.0.0.1:$port" nc -z 127.0.0.1 "$port" | ||||||||
|
|
||||||||
| local i=0 reqs=100 body | ||||||||
| while [ "$i" -lt "$reqs" ]; do | ||||||||
| body="$(curl -fsS "http://127.0.0.1:$port/")" \ | ||||||||
| || fail "node server request $i failed" | ||||||||
| [ "$body" = elfuse-node-server-ok ] \ | ||||||||
| || fail "node server request $i returned '$body'" | ||||||||
| i=$((i + 1)) | ||||||||
| done | ||||||||
| printf 'node server answered %d requests\n' "$reqs" | ||||||||
|
|
||||||||
| # The guest can reset /quit while exiting successfully. | ||||||||
| curl -fsS -o /dev/null "http://127.0.0.1:$port/quit" || true | ||||||||
| await_server_exit "node server after /quit" | ||||||||
| } | ||||||||
|
|
||||||||
| run_redis() | ||||||||
| { | ||||||||
| # Redis treats port 0 as disabled. | ||||||||
| local port="" try=0 | ||||||||
| while [ "$try" -lt 10 ]; do | ||||||||
| port=$((20000 + RANDOM % 20000)) | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a race between the |
||||||||
| if ! nc -z 127.0.0.1 "$port" 2> /dev/null; then | ||||||||
| break | ||||||||
| fi | ||||||||
| port="" | ||||||||
| try=$((try + 1)) | ||||||||
| done | ||||||||
| [ -n "$port" ] || fail "no free loopback port for redis after 10 probes" | ||||||||
|
|
||||||||
| # Make BGSAVE the only background save. | ||||||||
| start_server 'Ready to accept connections' \ | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||||
| "$BIN" run --entrypoint /usr/local/bin/redis-server "$MIRROR/redis:7-alpine" \ | ||||||||
| --bind 127.0.0.1 --port "$port" --save '' --dir /data | ||||||||
| wait_for 30 "redis server on 127.0.0.1:$port" nc -z 127.0.0.1 "$port" | ||||||||
|
|
||||||||
| run_capture redis-driver elfuse-oci-redis-workload-ok \ | ||||||||
| "$BIN" run --entrypoint /bin/sh "$MIRROR/redis:7-alpine" \ | ||||||||
| -c "$(cat "$WL/redis-driver.sh")" sh "$port" | ||||||||
|
|
||||||||
| await_server_exit "redis server after SHUTDOWN" | ||||||||
| } | ||||||||
|
|
||||||||
| command -v nc > /dev/null 2>&1 || { | ||||||||
| echo "nc is required" >&2 | ||||||||
| exit 2 | ||||||||
| } | ||||||||
|
|
||||||||
| "$BIN" clean > /dev/null | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||
|
|
||||||||
| case "$key" in | ||||||||
| python) | ||||||||
| run_capture python elfuse-oci-python-workload-ok \ | ||||||||
| "$BIN" run --entrypoint /usr/local/bin/python3 "$MIRROR/python:3.12-slim" \ | ||||||||
| -c "$(cat "$WL/python-workload.py")" | ||||||||
|
|
||||||||
| # A socket path too deep for the host's sun_path is shortened there and | ||||||||
| # must read back as the guest bound it. | ||||||||
| run_capture 'unix socket' elfuse-unix-sock-ok \ | ||||||||
| "$BIN" run --entrypoint /usr/local/bin/python3 "$MIRROR/python:3.12-slim" \ | ||||||||
| -c "$(cat "$WL/unix-socket.py")" | ||||||||
| ;; | ||||||||
| node) run_node ;; | ||||||||
| redis) run_redis ;; | ||||||||
| go) | ||||||||
| run_capture go elfuse-oci-go-workload-ok \ | ||||||||
| "$BIN" run "$MIRROR/golang:1.23-alpine" /bin/sh -c "$(cat "$WL/go-workload.sh")" | ||||||||
| ;; | ||||||||
| jvm) | ||||||||
| run_capture jvm elfuse-oci-jvm-workload-ok \ | ||||||||
| "$BIN" run "$MIRROR/eclipse-temurin:21" /bin/sh -c "$(cat "$WL/jvm-workload.sh")" | ||||||||
| ;; | ||||||||
| c) | ||||||||
| run_capture c elfuse-oci-c-workload-ok \ | ||||||||
| "$BIN" run "$MIRROR/gcc:14" /bin/sh -c "$(cat "$WL/c-workload.sh")" | ||||||||
| ;; | ||||||||
| *) fail "unknown runtime key: $key" ;; | ||||||||
| esac | ||||||||
|
|
||||||||
| "$BIN" clean > /dev/null | ||||||||
|
|
||||||||
| echo "runtime $key OK" | ||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curlhas no deadline, so a guest that accepts the connection and then stops responding holds the self-hosted runner until the job timeout. The/quitrequest below can hang the same way beforeawait_server_exitgets to start its 10 second check. Add--connect-timeout 5 --max-time 10to both calls.