Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/actions/read-tool-versions/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: Read tool versions from Makefile
description: Export KIND_VERSION, KUSTOMIZE_VERSION, and GRPCURL_VERSION from controller/Makefile into $GITHUB_ENV
runs:
using: composite
steps:
- run: sed -nE 's/^(KIND_VERSION|KUSTOMIZE_VERSION|GRPCURL_VERSION) \?= (.*)/\1=\2/p' controller/Makefile >> "$GITHUB_ENV"
shell: bash
1 change: 1 addition & 0 deletions .github/workflows/build-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ jobs:
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: .go-version
cache-dependency-path: "**/go.sum"

- name: Build operator installer manifest
if: ${{ matrix.generate_installer && steps.check.outputs.skip != 'true' }}
Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ permissions:

env:
CONTAINER_TOOL: docker
# Retry a failed spec once before failing the job. Retries are reported as
# flakes in the ginkgo summary, so this hides nothing; it only stops a single
# infrastructure hiccup from failing an unrelated PR.
E2E_FLAKE_ATTEMPTS: "2"

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.

nice addition! :)

# Run the non-Serial containers two at a time. The runner has 4 CPUs and the
# suite spends most of its time waiting on the cluster, so a second process
# overlaps the two long containers (hooks, core) rather than competing for CPU.
E2E_PROCS: "2"

jobs:
changes:
Expand Down Expand Up @@ -68,6 +76,7 @@ jobs:
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: .go-version
cache-dependency-path: "**/go.sum"

- name: Cache controller image
id: cache
Expand Down Expand Up @@ -107,6 +116,7 @@ jobs:
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: .go-version
cache-dependency-path: "**/go.sum"

- name: Cache operator artifacts
id: cache
Expand Down Expand Up @@ -151,6 +161,7 @@ jobs:
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: .go-version
cache-dependency-path: "**/go.sum"

- name: Cache exporterset-controller image
id: cache
Expand Down Expand Up @@ -310,6 +321,7 @@ jobs:
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: .go-version
cache-dependency-path: "**/go.sum"

- name: Load e2e artifacts
uses: ./.github/actions/load-e2e-artifacts
Expand All @@ -327,6 +339,15 @@ jobs:
sudo modprobe "$mod" 2>/dev/null || true
done

- name: Read tool versions from Makefile
uses: ./.github/actions/read-tool-versions

- name: Cache controller tools
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: controller/bin
key: controller-tools-${{ matrix.arch }}-kind${{ env.KIND_VERSION }}-kustomize${{ env.KUSTOMIZE_VERSION }}-grpcurl${{ env.GRPCURL_VERSION }}

- name: Setup e2e test environment
run: make e2e-setup
env:
Expand Down Expand Up @@ -374,6 +395,16 @@ jobs:
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: .go-version
cache-dependency-path: "**/go.sum"

- name: Read tool versions from Makefile
uses: ./.github/actions/read-tool-versions

- name: Cache controller tools
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: controller/bin
key: controller-tools-amd64-kind${{ env.KIND_VERSION }}-kustomize${{ env.KUSTOMIZE_VERSION }}-grpcurl${{ env.GRPCURL_VERSION }}

- name: Setup compat environment (old controller v0.8.1)
run: make e2e-compat-setup COMPAT_SCENARIO=old-controller
Expand Down Expand Up @@ -416,12 +447,22 @@ jobs:
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: .go-version
cache-dependency-path: "**/go.sum"

- name: Load e2e artifacts
uses: ./.github/actions/load-e2e-artifacts
with:
arch: amd64

- name: Read tool versions from Makefile
uses: ./.github/actions/read-tool-versions

- name: Cache controller tools
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: controller/bin
key: controller-tools-amd64-kind${{ env.KIND_VERSION }}-kustomize${{ env.KUSTOMIZE_VERSION }}-grpcurl${{ env.GRPCURL_VERSION }}

- name: Setup compat environment (old client v0.7.4)
run: make e2e-compat-setup COMPAT_SCENARIO=old-client
env:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ jobs:
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: .go-version
cache-dependency-path: "**/go.sum"

- name: Run go linter
working-directory: controller
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release-operator-installer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: .go-version
cache-dependency-path: "**/go.sum"

- name: Build operator installer manifest
env:
Expand Down
33 changes: 25 additions & 8 deletions controller/hack/deploy_with_operator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,40 @@ if [ "${USE_CERTMANAGER}" = "true" ]; then
fi
fi

# load the container images into the cluster
load_image "${IMG}"
load_image "${OPERATOR_IMG}"
load_image "${EXPORTER_SET_CONTROLLER_IMG}"
# Exporter + QEMU runtime images are required for ExporterSet QEMU e2e / samples.
# Missing images are skipped so plain controller deploys still work.
# Load container images into the cluster in parallel. Each `kind load` is I/O
# bound (piping a tarball into the node's containerd), so overlapping them cuts
# wall-clock to roughly the cost of the single largest image.
_load_pids=()
_load_failed=0

load_image "${IMG}" &
_load_pids+=($!)
load_image "${OPERATOR_IMG}" &
_load_pids+=($!)
load_image "${EXPORTER_SET_CONTROLLER_IMG}" &
_load_pids+=($!)

if container_image_exists "${EXPORTER_IMG}"; then
load_image "${EXPORTER_IMG}"
load_image "${EXPORTER_IMG}" &
_load_pids+=($!)
else
echo -e "${YELLOW}Skipping load of exporter image (not present locally): ${EXPORTER_IMG}${NC}"
fi
if container_image_exists "${QEMU_RUNTIME_IMG}"; then
load_image "${QEMU_RUNTIME_IMG}"
load_image "${QEMU_RUNTIME_IMG}" &
_load_pids+=($!)
else
echo -e "${YELLOW}Skipping load of qemu-runtime image (not present locally): ${QEMU_RUNTIME_IMG}${NC}"
fi

for pid in "${_load_pids[@]}"; do
wait "${pid}" || _load_failed=1
done
if [ "${_load_failed}" -eq 1 ]; then
echo -e "${RED}One or more images failed to load${NC}"
exit 1
fi

# Deploy the operator
echo -e "${GREEN}Deploying Jumpstarter operator ...${NC}"
kubectl apply -f deploy/operator/dist/install.yaml
Expand Down
18 changes: 17 additions & 1 deletion e2e/lib/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,23 @@ run_ginkgo() {
timeout="60m"
fi

local flags=(-v --show-node-events --trace --timeout "${timeout}")
# Retry a failed spec instead of failing the whole suite. The e2e suite talks
# to a real cluster over the network, so a spec can fail for reasons that have
# nothing to do with the code under test (a slow DNS answer, a pod scheduled
# late, a router connection dropped). A retried spec is still reported as
# flaky in the summary, so genuine instability stays visible.
local flake_attempts="${E2E_FLAKE_ATTEMPTS:-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.

I think we should set it to 2, since ginkgo treats it as the total number of runs rather than the number of retries, otherwise we'll end up with the same behavior as before.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

we have it set to 2 in the workflow yaml, it will default to 1 only for local testing and such


# Run top-level containers concurrently when asked. Off by default: the
# suite shares one cluster and one runner, so more processes is not free.
# Containers that touch host-global state or the shared client config are
# marked Serial and still run one at a time, after the parallel ones.
local procs="${E2E_PROCS:-1}"

local flags=(-v --show-node-events --trace --timeout "${timeout}" --flake-attempts "${flake_attempts}")
if [ "${procs}" -gt 1 ]; then
flags+=(--procs "${procs}")
fi
if [ -n "$label_filter" ]; then
flags+=(--label-filter "$label_filter")
fi
Expand Down
30 changes: 30 additions & 0 deletions e2e/setup-e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,35 @@ deploy_controller() {
# shellcheck source=lib/install.sh
source "$SCRIPT_DIR/lib/install.sh"

# Pin the ingress hostnames in /etc/hosts so that host-side clients (jmp, curl)
# never depend on public DNS.
#
# baseDomain is a nip.io wildcard name of the form jumpstarter.<IP>.nip.io, so
# every jmp invocation would otherwise resolve <prefix>.jumpstarter.<IP>.nip.io
# against a public resolver. A slow or rate-limited lookup burns the client's
# whole connect budget and surfaces as "Timeout connecting to grpc....:8082".
# The IP is already embedded in the name, so we can serve the same answer
# locally. Non-nip.io base domains are left alone.
pin_basedomain_hosts_entries() {
local basedomain="$1"
local ip

ip=$(echo "${basedomain}" | sed -nE 's/^.*\.([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\.nip\.io$/\1/p')
if [ -z "${ip}" ]; then
log_info "baseDomain ${basedomain} is not a nip.io name, leaving DNS resolution alone"
return 0
fi

if grep -Fq "grpc.${basedomain}" /etc/hosts 2>/dev/null; then
log_info "✓ ${basedomain} entries already in /etc/hosts"
return 0
fi

log_warn "About to add ${basedomain} entries to /etc/hosts (requires sudo)"
echo "${ip} ${basedomain} grpc.${basedomain} router.${basedomain} login.${basedomain}" | sudo tee -a /etc/hosts
log_info "✓ Pinned ${basedomain} to ${ip} in /etc/hosts"
}

# Step 6: Setup test environment
setup_test_environment() {
log_info "Setting up test environment..."
Expand All @@ -290,6 +319,7 @@ setup_test_environment() {
log_error "Failed to get baseDomain from Jumpstarter CR in namespace ${JS_NAMESPACE}"
exit 1
fi
pin_basedomain_hosts_entries "${BASEDOMAIN}"
export ENDPOINT="grpc.${BASEDOMAIN}:8082"
export LOGIN_ENDPOINT="login.${BASEDOMAIN}:8086"
log_info "Controller endpoint: $ENDPOINT"
Expand Down
2 changes: 1 addition & 1 deletion e2e/test/auth_logging_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import (
// with legacy (controller-issued) tokens so the token lives in a local config
// file where the test can corrupt it. It is intentionally NOT labelled for the
// compat suites — old controller images do not have auth-failure logging.
var _ = Describe("Auth Failure Logging E2E Tests", Label("auth-logging"), Ordered, func() {
var _ = Describe("Auth Failure Logging E2E Tests", Label("auth-logging"), Ordered, ContinueOnFailure, func() {
const (
clientName = "test-client-authlog"
exporterName = "test-exporter-authlog"
Expand Down
3 changes: 2 additions & 1 deletion e2e/test/compat_old_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import (
. "github.com/onsi/gomega" //nolint:revive
)

var _ = Describe("Compat: Old Client E2E Tests", Label("compat", "old-client"), Ordered, func() {
// Serial: installs an older client into the shared environment.
var _ = Describe("Compat: Old Client E2E Tests", Label("compat", "old-client"), Ordered, Serial, func() {
var (
tracker *ProcessTracker
ns string
Expand Down
3 changes: 2 additions & 1 deletion e2e/test/compat_old_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import (
. "github.com/onsi/gomega" //nolint:revive
)

var _ = Describe("Compat: Old Controller E2E Tests", Label("compat", "old-controller"), Ordered, func() {
// Serial: replaces the running controller and switches the active client.
var _ = Describe("Compat: Old Controller E2E Tests", Label("compat", "old-controller"), Ordered, Serial, func() {
var (
tracker *ProcessTracker
ns string
Expand Down
2 changes: 1 addition & 1 deletion e2e/test/direct_listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
. "github.com/onsi/gomega" //nolint:revive
)

var _ = Describe("Direct Listener E2E Tests", Label("direct-listener"), Ordered, func() {
var _ = Describe("Direct Listener E2E Tests", Label("direct-listener"), Ordered, ContinueOnFailure, func() {
var (
tracker *ProcessTracker
listenerPort = 19090
Expand Down
4 changes: 3 additions & 1 deletion e2e/test/dut_network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ func sudoArgs(args ...string) (string, []string) {
return args[0], args[1:]
}

var _ = Describe("DUT Network E2E Tests", Label("dut-network"), Ordered, func() {
// Serial: builds veth pairs, bridges and nftables rules in the host network
// namespace, and drives dnsmasq. There is only one host to share.
var _ = Describe("DUT Network E2E Tests", Label("dut-network"), Ordered, ContinueOnFailure, Serial, func() {
var (
tracker *ProcessTracker
listenerPort = 19091
Expand Down
Loading
Loading