Skip to content
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

Add XDP integration test #1078

Merged
merged 6 commits into from
Jan 28, 2025
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
15 changes: 15 additions & 0 deletions .ci/xdp/cluster-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
# Proxy
- role: worker
# Client
- role: worker
# Server
- role: worker
# https://github.com/helm/kind-action?tab=readme-ov-file#configuring-local-registry
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d"
82 changes: 82 additions & 0 deletions .ci/xdp/integration-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/bin/bash
set -eu

source="${BASH_SOURCE[0]}"

proxy_image="localhost:$REGISTRY_PORT/quilkin:ci"

echo "::notice file=$source,line=$LINENO::Building quilkin proxy"
cargo build -p quilkin --bin quilkin
# strip the binary to reduce copy times into the dockerfile
strip ./target/debug/quilkin

echo "::notice file=$source,line=$LINENO::Building quilkin image"
docker build -f .ci/xdp/proxy.dockerfile -t "${proxy_image}" .
echo "::notice file=$source,line=$LINENO::Pushing quilkin image"
docker push "${proxy_image}"

echo "::notice file=$source,line=$LINENO::Starting UDP echo server"
kubectl apply --context "$CLUSTER" -f .ci/xdp/server.yaml
kubectl wait --context "$CLUSTER" --for=condition=ready pod/echo-server

server_ip=$(kubectl get --context "$CLUSTER" pod echo-server --template '{{.status.podIP}}')

echo "::notice file=$source,line=$LINENO::Starting quilkin proxy"
kubectl apply --context "$CLUSTER" -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: proxy
spec:
containers:
- name: proxy
image: "$proxy_image"
ports:
- containerPort: 7777
hostPort: 7777
args: ["proxy", "--to", "${server_ip}:8078", "--service.udp.xdp"]
securityContext:
capabilities:
add:
- CAP_BPF # We load an eBPF program
- CAP_NET_RAW # We create SOCK_RAW (XDP) sockets
EOF

kubectl wait --context "$CLUSTER" --for=condition=ready pod/proxy

proxy_ip=$(kubectl get --context "$CLUSTER" pod proxy --template '{{.status.podIP}}')

echo "::notice file=$source,line=$LINENO::Running UDP client"
kubectl apply --context "$CLUSTER" -f - <<EOF
apiVersion: v1
kind: Job
metadata:
name: client
spec:
containers:
- name: client
image: fortio/fortio
args: ["load", "-n", "100", "udp://${proxy_ip}:7777"]
EOF

logs=$(kubectl logs -f --context "$CLUSTER" job/client)

echo "::notice file=$source,line=$LINENO::Finished sending client requests"

# Total Bytes sent: 30, received: 0
regex="Total Bytes sent: (\d+), received: (\d+)"

if [[ $logs =~ $regex ]]; then
send=${BASH_REMATCH[1]}
recv=${BASH_REMATCH[2]}
if [[ $send -eq $recv ]]; then
echo "::notice file=$source,line=$LINENO::Successfully sent and received {$recv}b"
exit 0
fi

echo "::error file=$source,line=$LINENO::sent ${send}b but only received ${recv}b"
exit 1
fi

echo "::error file=$source,line=$LINENO::Failed to find expected log line from UDP client"
exit 2
3 changes: 3 additions & 0 deletions .ci/xdp/proxy.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM debian:bookworm-slim
COPY ./target/debug/quilkin /usr/local/bin
ENTRYPOINT quilkin
2 changes: 2 additions & 0 deletions .ci/xdp/proxy.dockerfile.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# This file exists purely to ignore the root .dockerignore otherwise it will ignore
# target/debug/quilkin when copying it into the image
13 changes: 13 additions & 0 deletions .ci/xdp/server.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v1
kind: Pod
metadata:
name: echo-server
spec:
containers:
- name: echo-server
image: fortio/fortio
ports:
- containerPort: 8078
hostPort: 8078
args: ["udp-echo"]

47 changes: 47 additions & 0 deletions .ci/xdp/spinup-cluster-with-registry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/bin/bash
set -eu

# 1. Create registry container unless it already exists
if [ "$(docker inspect -f '{{.State.Running}}' "${CLUSTER_DESIRED}" 2>/dev/null || true)" != 'true' ]; then
docker run \
-d --restart=always -p "127.0.0.1:${REGISTRY_PORT}:5000" --network bridge --name "${REGISTRY_NAME}" \
registry:2
fi

kind create cluster --name "${CLUSTER_DESIRED}" --config=.ci/xdp/cluster-config.yaml

# 3. Add the registry config to the nodes
#
# This is necessary because localhost resolves to loopback addresses that are
# network-namespace local.
# In other words: localhost in the container is not localhost on the host.
#
# We want a consistent name that works from both ends, so we tell containerd to
# alias localhost:${reg_port} to the registry container when pulling images
REGISTRY_DIR="/etc/containerd/certs.d/localhost:${REGISTRY_PORT}"
for node in $(kind get nodes); do
docker exec "${node}" mkdir -p "${REGISTRY_DIR}"
cat <<EOF | docker exec -i "${node}" cp /dev/stdin "${REGISTRY_DIR}/hosts.toml"
[host."http://${REGISTRY_NAME}:5000"]
EOF
done

# 4. Connect the registry to the cluster network if not already connected
# This allows kind to bootstrap the network but ensures they're on the same network
if [ "$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' "${REGISTRY_NAME}")" = 'null' ]; then
docker network connect "kind" "${REGISTRY_NAME}"
fi

# 5. Document the local registry
# https://github.com/kubernetes/enhancements/tree/master/keps/sig-cluster-lifecycle/generic/1755-communicating-a-local-registry
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: local-registry-hosting
namespace: kube-public
data:
localRegistryHosting.v1: |
host: "localhost:${REGISTRY_PORT}"
help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF
63 changes: 63 additions & 0 deletions .ci/xdp/veth-integ-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/bin/bash
set -e

source="${BASH_SOURCE[0]}"

cleanup() {
echo "Cleaning up"
ip netns del cs || true
ip netns del proxy || true

pkill fortio || true
pkill quilkin || true
}

trap cleanup EXIT

ip netns del cs || true
ip netns del proxy || true

echo "::notice file=$source,line=$LINENO::Creating network namespaces"

Check notice on line 20 in .ci/xdp/veth-integ-test.sh

View workflow job for this annotation

GitHub Actions / xdp-integration

Creating network namespaces
ip netns add cs
ip netns add proxy

echo "::notice file=$source,line=$LINENO::Adding client <-> proxy <-> server links"

Check notice on line 24 in .ci/xdp/veth-integ-test.sh

View workflow job for this annotation

GitHub Actions / xdp-integration

Adding client <-> proxy <-> server links
ip link add veth-cs type veth peer name veth-proxy

ip link set veth-cs netns cs
ip link set veth-proxy netns proxy

echo "::notice file=$source,line=$LINENO::Adding IPs"

Check notice on line 30 in .ci/xdp/veth-integ-test.sh

View workflow job for this annotation

GitHub Actions / xdp-integration

Adding IPs
ip -n cs addr add 10.0.0.1/24 dev veth-cs
ip -n proxy addr add 10.0.0.2/24 dev veth-proxy

echo "::notice file=$source,line=$LINENO::Creating network namespaces"

Check notice on line 34 in .ci/xdp/veth-integ-test.sh

View workflow job for this annotation

GitHub Actions / xdp-integration

Creating network namespaces
ip -n cs link set veth-cs up
ip -n proxy link set veth-proxy up

ip netns exec cs fortio udp-echo&
ip netns exec proxy ./target/debug/quilkin proxy --to 10.0.0.1:8078 --publish.udp.xdp&

echo "::notice file=$source,line=$LINENO::Launching client"

Check notice on line 41 in .ci/xdp/veth-integ-test.sh

View workflow job for this annotation

GitHub Actions / xdp-integration

Launching client
ip netns exec cs fortio load -n 10 udp://10.0.0.2:7777 2> ./target/logs.txt
logs=$(cat ./target/logs.txt)
echo "$logs"

regex="Total Bytes sent: ([0-9]+), received: ([0-9]+)"

if [[ $logs =~ $regex ]]; then
send=${BASH_REMATCH[1]}
recv=${BASH_REMATCH[2]}
# We could be more strict here and require they are exactly equal, but I can't
# even consistently get that on my local machine so I doubt CI will fair better
if [[ $recv -ne "0" ]]; then
echo "::notice file=$source,line=$LINENO::Successfully sent ${send}B and received ${recv}B"

Check notice on line 54 in .ci/xdp/veth-integ-test.sh

View workflow job for this annotation

GitHub Actions / xdp-integration

Successfully sent 240B and received 240B
exit 0
fi

echo "::error file=$source,line=$LINENO::sent ${send}B but only received ${recv}B"
exit 1
fi

echo "::error file=$source,line=$LINENO::Failed to find expected log line from UDP client"
exit 2
46 changes: 46 additions & 0 deletions .github/workflows/xdp-integration-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: xdp-integration-test

on:
push:
branches:
- "main"
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always

jobs:
xdp-integration:
runs-on: ubuntu-24.04
# env:
# CLUSTER_DESIRED: xdp-integ
# CLUSTER: kind-xdp-integ
# SERVER_PORT: "8078"
# REGISTRY_PORT: "5001"
# REGISTRY_NAME: registry
steps:
- uses: actions/checkout@v4
# - name: Create cluster
# id: kind
# uses: helm/kind-action@v1
# with:
# config: .ci/xdp/cluster-config.yaml
# cluster_name: ${{ env.CLUSTER_DESIRED }}
# registry: true
# registry_name: ${{ env.REGISTRY_NAME }}
# registry_port: ${{ env.REGISTRY_PORT }}
- uses: dtolnay/rust-toolchain@stable
- name: Fetch quilkin
run: cargo fetch --target x86_64-unknown-linux-gnu
- name: Build quilkin
run: cargo build -p quilkin --bin quilkin
- name: Install fortio
run: |
curl -L -o fortio.deb https://github.com/fortio/fortio/releases/download/v1.68.0/fortio_1.68.0_amd64.deb
sudo dpkg -i fortio.deb
- name: Run XDP integration test
run: sudo .ci/xdp/veth-integ-test.sh
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
!.gcloudignore
!.dockerignore
!.github
!.ci
!xds/ci/.golangci.yaml

*.iml
Expand Down
Loading