Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .github/actions/setup-e2e-kind/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ inputs:
cluster-name:
description: kind cluster name
required: true
config:
description: Optional kind cluster configuration file
required: false
default: ""
image-tag:
description: OpenShell image tag to load
required: true
Expand Down Expand Up @@ -49,6 +53,7 @@ runs:
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with:
cluster_name: ${{ inputs.cluster-name }}
config: ${{ inputs.config }}
wait: 120s

- name: Export kind kubeconfig to mise path
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/e2e-kubernetes-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ on:
type: string
default: ""
use-envoy-gateway:
description: "Route the test through Envoy Gateway instead of a direct gateway Service port-forward"
description: "Route the test through Envoy Gateway instead of the direct gateway NodePort"
required: false
type: boolean
default: false
Expand Down Expand Up @@ -124,6 +124,7 @@ jobs:
uses: ./.github/actions/setup-e2e-kind
with:
cluster-name: ${{ env.KIND_CLUSTER_NAME }}
config: ${{ !inputs.use-envoy-gateway && 'e2e/kind-nodeport.yaml' || '' }}
image-tag: ${{ inputs.image-tag }}
images: ${{ inputs.cluster-images }}
registry-username: ${{ github.actor }}
Expand All @@ -134,6 +135,7 @@ jobs:
env:
AGENT_SANDBOX_VERSION: ${{ inputs.agent-sandbox-version }}
OPENSHELL_E2E_KUBE_CONTEXT: kind-${{ env.KIND_CLUSTER_NAME }}
OPENSHELL_E2E_KUBE_NODEPORT_PORT: ${{ !inputs.use-envoy-gateway && '17670' || '' }}
OPENSHELL_E2E_KUBE_EXTRA_VALUES: ${{ inputs.extra-helm-values }}
OPENSHELL_E2E_KUBE_EXTERNAL_POSTGRES_SECRET: ${{ inputs.external-postgres-secret }}
OPENSHELL_E2E_KUBE_TEST: ${{ inputs.test-name }}
Expand Down
2 changes: 1 addition & 1 deletion CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ Three opt-in labels enable the long-running E2E suites:
(`replicaCount: 2` and bundled PostgreSQL) and the credential-driver suite
(Kubernetes Secrets plus Vault) in `Branch E2E Checks`

When multiple labels are present, `Branch E2E Checks` builds each generic multi-architecture artifact set once and fans out enabled suites in parallel. Runtime-specific reusable workflows define the Docker, Podman, VM, and Kubernetes lanes. Composite actions own the replaceable Podman, KVM, kind, and mise setup. Each lane depends only on the artifact categories it consumes: VM does not wait for container-driver artifacts or supervisor images, and GPU does not wait for the gateway image. Docker, Podman, GPU, Rust, Python, MCP, and VM E2E reuse matching prebuilt gateway and CLI binaries instead of compiling debug binaries in test jobs. Standalone-driver lanes additionally reuse driver-free gateway and compute-driver artifacts. Kubernetes managed-driver lanes consume published gateway and supervisor images, while the standalone-driver lane composes its gateway image from prebuilt binaries.
When multiple labels are present, `Branch E2E Checks` builds each generic multi-architecture artifact set once and fans out enabled suites in parallel. Runtime-specific reusable workflows define the Docker, Podman, VM, and Kubernetes lanes. Composite actions own the replaceable Podman, KVM, kind, and mise setup. Direct Kubernetes E2E lanes use a kind NodePort mapped to runner localhost (`17670` → `30080`) for gateway gRPC/SSH traffic; health checks still use port-forwarding. Envoy Gateway lanes retain their proxy Service port-forward. Each lane depends only on the artifact categories it consumes: VM does not wait for container-driver artifacts or supervisor images, and GPU does not wait for the gateway image. Docker, Podman, GPU, Rust, Python, MCP, and VM E2E reuse matching prebuilt gateway and CLI binaries instead of compiling debug binaries in test jobs. Standalone-driver lanes additionally reuse driver-free gateway and compute-driver artifacts. Kubernetes managed-driver lanes consume published gateway and supervisor images, while the standalone-driver lane composes its gateway image from prebuilt binaries.
The `OpenShell / E2E` and `OpenShell / GPU E2E` required statuses are evaluated from separate suite result jobs inside that workflow. `test:e2e-kubernetes` is optional while Kubernetes HA and credential-driver behavior are under active iteration: failures are visible in the workflow run but do not publish a required CI gate status.

The GitHub ruleset should require the `OpenShell / ...` statuses published by
Expand Down
13 changes: 13 additions & 0 deletions e2e/kind-nodeport.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
# Match service.nodePort in with-kube-gateway.sh. Keep the host listener local.
- containerPort: 30080
hostPort: 17670
listenAddress: "127.0.0.1"
protocol: TCP
52 changes: 43 additions & 9 deletions e2e/with-kube-gateway.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
# Modes:
# - OPENSHELL_E2E_KUBE_CONTEXT set:
# Target the named kubectl context, install the chart into an ephemeral
# namespace, and port-forward the gateway. Cluster lifecycle is the
# namespace, and connect through the configured gateway transport. Cluster lifecycle is the
# caller's responsibility (e.g. CI provisions kind via helm/kind-action).
# - OPENSHELL_E2E_KUBE_CONTEXT unset:
# Create a local k3d cluster via tasks/scripts/helm-k3s-local.sh, install
# the chart, port-forward, and tear the cluster down on exit.
#
# On vanilla Kubernetes, Helm e2e talks to the gateway in plaintext over
# `kubectl port-forward` (ci/values-skaffold.yaml). The certgen hook still runs
# a mapped NodePort in kind CI, or `kubectl port-forward` for other clusters
# (ci/values-skaffold.yaml). The certgen hook still runs
# so the gateway has sandbox JWT signing keys.
#
# On OpenShift, that port-forward is too slow for `sandbox connect`. That command
Expand All @@ -28,6 +29,9 @@
# Every OpenShift-specific branch below is gated on OPENSHIFT_DETECTED, so the
# vanilla-Kubernetes path stays exactly the same.
#
# Set OPENSHELL_E2E_KUBE_NODEPORT_PORT to the localhost port mapped to gateway
# NodePort 30080 (kind CI uses 17670). This requires a matching cluster port map.
#
# Set OPENSHELL_E2E_KUBE_EXTRA_VALUES to one or more colon-separated Helm values
# files, relative to the repository root or absolute, to layer additional chart
# configuration on top of ci/values-skaffold.yaml.
Expand Down Expand Up @@ -307,14 +311,30 @@ wait_for_envoy_service() {
return 1
}

start_gateway_portforward() {
start_gateway_connection() {
local elapsed=0
local pf_timeout=30
local target_port=8080
local target_namespace="${NAMESPACE}"
local target_service="${RELEASE_NAME}"
local target_service_ref=""

# kind CI maps this NodePort to localhost so SSH streams avoid kubectl's
# port-forward transport. Local/external clusters retain their existing path.
if [ -n "${OPENSHELL_E2E_KUBE_NODEPORT_PORT:-}" ]; then
LOCAL_PORT="${OPENSHELL_E2E_KUBE_NODEPORT_PORT}"
echo "Waiting for mapped gateway NodePort at 127.0.0.1:${LOCAL_PORT}..."
while [ "${elapsed}" -lt "${pf_timeout}" ]; do
if curl -s -o /dev/null --connect-timeout 1 "http://127.0.0.1:${LOCAL_PORT}"; then
return 0
fi
sleep 1
elapsed=$((elapsed + 1))
done
echo "ERROR: mapped gateway NodePort did not accept TCP within ${pf_timeout}s" >&2
return 1
fi

LOCAL_PORT="$(e2e_pick_port)"
if use_envoy_gateway; then
target_service_ref="$(wait_for_envoy_service)"
Expand Down Expand Up @@ -697,7 +717,7 @@ scenario_cleanup_external_pg() {
cleanup_postgres_fixture my-pg-credentials
}

# Run a single DB-backend scenario: install chart → port-forward → run tests → cleanup.
# Run a single DB-backend scenario: install chart → connect → run tests → cleanup.
# Usage: run_scenario "label" "type" [extra --set flags...]
# type: sqlite | external-pg
run_scenario() {
Expand Down Expand Up @@ -731,9 +751,9 @@ run_scenario() {
return
fi
else
# Vanilla Kubernetes: reach the gateway in plaintext over port-forward.
if ! start_gateway_portforward; then
scenario_record_failure "${scenario_label}" "port-forward failed"
# Vanilla Kubernetes: use a mapped NodePort in kind CI, otherwise port-forward.
if ! start_gateway_connection; then
scenario_record_failure "${scenario_label}" "gateway connection failed"
return
fi
GATEWAY_NAME="openshell-e2e-kube-${LOCAL_PORT}"
Expand Down Expand Up @@ -1406,6 +1426,20 @@ if [ -n "${OPENSHELL_E2E_KUBE_EXTRA_VALUES:-}" ]; then
helm_values_args+=(--values "${values_file}")
done
fi
if [ -n "${OPENSHELL_E2E_KUBE_NODEPORT_PORT:-}" ]; then
if use_envoy_gateway || [ "${OPENSHIFT_DETECTED}" = "1" ]; then
echo "ERROR: mapped gateway NodePort requires a direct vanilla Kubernetes gateway" >&2
exit 2
fi
if ! [[ "${OPENSHELL_E2E_KUBE_NODEPORT_PORT}" =~ ^[0-9]+$ ]] ||
[ "${OPENSHELL_E2E_KUBE_NODEPORT_PORT}" -lt 1 ] ||
[ "${OPENSHELL_E2E_KUBE_NODEPORT_PORT}" -gt 65535 ]; then
echo "ERROR: OPENSHELL_E2E_KUBE_NODEPORT_PORT must be a TCP port (1-65535)" >&2
exit 2
fi
# These overrides must follow extra values so the Service matches kind's map.
helm_values_args+=(--set "service.type=NodePort" --set "service.nodePort=30080")
fi
if use_envoy_gateway; then
helm_values_args+=(--values "${ROOT}/deploy/helm/openshell/ci/values-gateway.yaml")
install_envoy_gateway
Expand Down Expand Up @@ -1481,8 +1515,8 @@ else
# SSH-relay `sandbox connect` suites work (port-forward stalls them).
openshift_register_route_gateway || exit 1
else
# Vanilla Kubernetes: reach the gateway in plaintext over port-forward.
start_gateway_portforward || exit 1
# Vanilla Kubernetes: use a mapped NodePort in kind CI, otherwise port-forward.
start_gateway_connection || exit 1
GATEWAY_NAME="openshell-e2e-kube-${LOCAL_PORT}"
GATEWAY_ENDPOINT="http://127.0.0.1:${LOCAL_PORT}"
e2e_register_plaintext_gateway \
Expand Down
9 changes: 9 additions & 0 deletions skills/debug-openshell-cluster/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,15 @@ kubectl -n openshell get svc openshell -o wide
kubectl -n openshell get endpoints openshell
```

For kind CI diagnostics, direct Kubernetes E2E lanes expose the gateway through
NodePort `30080`, mapped to runner `127.0.0.1:17670`. The runner registers that
endpoint when `OPENSHELL_E2E_KUBE_NODEPORT_PORT=17670`; gateway health checks
still use port-forwarding. Envoy Gateway lanes retain their proxy Service
port-forward. Inspect the NodePort mapping and Service if the direct CI endpoint
is unreachable. Port-forwarding can delay SSH session cleanup and cause a
replacement connection to attach read-only, so compare transports when debugging
reconnect failures.

For local port-forward testing:

```bash
Expand Down
Loading