Skip to content

Commit dea93e2

Browse files
ZPascalclaude
andcommitted
ci: Add GitHub workflow for integration tests on multiple Java versions
Run the integration tests against a Cloud Foundry deployed on KinD (cloudfoundry/kind-deployment) in a Java 8/11/17/21 matrix. Every job gets its own Cloud Foundry. - Validation runs deploy the capi-release matching SUPPORTED_API_VERSION (resolved by .github/scripts/resolve-capi-version.sh); scheduled and manual preview runs use the latest versions. - Raise the Postgres max_connections of the KinD deployment and restart its clients: the shared database otherwise runs out of connections during a full run. - Dump cluster diagnostics (including Postgres connections) on failure. - ci-java.yml calls the workflow after the build job. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent 4ea83a0 commit dea93e2

4 files changed

Lines changed: 193 additions & 10 deletions

File tree

‎.github/scripts/pg-psql.sh‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env bash
2+
# Runs psql as the postgres admin inside the Postgres pod that kind-deployment installs, passing
3+
# all arguments on to psql. The bitnami chart hands the admin password to the container under
4+
# different names (or as a file) depending on its auth settings, so try all of them.
5+
set -euo pipefail
6+
7+
kubectl -n cf-system exec postgresql-0 -c postgresql -- sh -c '
8+
for var in POSTGRES_POSTGRES_PASSWORD POSTGRES_PASSWORD; do
9+
eval "pw=\$$var"
10+
[ -n "$pw" ] && break
11+
eval "file=\$${var}_FILE"
12+
[ -n "$file" ] && pw="$(cat "$file")" && break
13+
done
14+
PGPASSWORD="$pw" exec psql -U postgres -h localhost "$@"
15+
' sh "$@"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env bash
2+
# Prints the newest capi-release version whose CC API (v2) version equals SUPPORTED_API_VERSION
3+
# from CloudFoundryClient.java. Needs only git and curl, no GitHub API and so no token.
4+
#
5+
# A capi-release tag pins cloud_controller_ng as a submodule, and that repo records its v2 API
6+
# version in config/version_v2. The release notes aren't reliable for this. The v2 version only
7+
# grows with the tags, so a binary search over the sorted tags is enough.
8+
set -euo pipefail
9+
10+
source_file="$(dirname "$0")/../../cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java"
11+
target="$(sed -n 's/.*String SUPPORTED_API_VERSION = "\([0-9.]*\)";.*/\1/p' "$source_file")"
12+
[ -n "$target" ] || { echo "SUPPORTED_API_VERSION not found in $source_file" >&2; exit 1; }
13+
14+
workdir="$(mktemp -d)"
15+
trap 'rm -rf "$workdir"' EXIT
16+
# commits and trees only, which is all it takes to read the submodule commit of a tag
17+
git clone --quiet --bare --filter=blob:none https://github.com/cloudfoundry/capi-release.git "$workdir/capi-release"
18+
19+
mapfile -t tags < <(git -C "$workdir/capi-release" tag -l | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V)
20+
21+
v2_version() {
22+
local sha
23+
sha="$(git -C "$workdir/capi-release" ls-tree "$1" src/cloud_controller_ng | awk '{print $3}')"
24+
curl -fsSL --retry 3 "https://raw.githubusercontent.com/cloudfoundry/cloud_controller_ng/$sha/config/version_v2" | tr -d '[:space:]'
25+
}
26+
27+
# highest index whose v2 version is <= target
28+
lo=0
29+
hi=$((${#tags[@]} - 1))
30+
found=-1
31+
while [ "$lo" -le "$hi" ]; do
32+
mid=$(((lo + hi) / 2))
33+
v="$(v2_version "${tags[$mid]}")"
34+
if [ "$(printf '%s\n%s\n' "$v" "$target" | sort -V | tail -1)" = "$target" ]; then
35+
found=$mid
36+
lo=$((mid + 1))
37+
else
38+
hi=$((mid - 1))
39+
fi
40+
done
41+
42+
if [ "$found" -lt 0 ] || [ "$(v2_version "${tags[$found]}")" != "$target" ]; then
43+
echo "No capi-release found for CC API version $target" >&2
44+
exit 1
45+
fi
46+
47+
echo "CC API $target -> capi-release ${tags[$found]}" >&2
48+
echo "${tags[$found]}"

‎.github/workflows/ci-java.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,9 @@ jobs:
4141
name: Check style with Spotless
4242
run: ./mvnw spotless:check -Pintegration-test
4343

44+
integration-test:
45+
needs: build
46+
uses: ./.github/workflows/integration-test.yml
47+
with:
48+
java-versions: '[21]'
49+

‎.github/workflows/integration-test.yml‎

Lines changed: 124 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,48 +5,162 @@ on:
55
inputs:
66
java-versions:
77
description: 'JSON array of Java versions to test'
8+
type: string
89
required: false
910
default: '[8, 11, 17, 21]'
11+
preview:
12+
description: 'Deploy the latest cf-deployment versions instead of the default ones'
13+
type: boolean
14+
required: false
15+
default: false
1016
schedule:
17+
# preview run: latest versions (including CAPI) on the latest JDK only
1118
- cron: '0 3 * * 1-5'
19+
workflow_call:
20+
inputs:
21+
java-versions:
22+
description: 'JSON array of Java versions to test'
23+
type: string
24+
required: false
25+
default: '[8, 11, 17, 21]'
26+
preview:
27+
description: 'Deploy the latest cf-deployment versions instead of the default ones'
28+
type: boolean
29+
required: false
30+
default: false
31+
32+
permissions:
33+
contents: read
34+
35+
# A new push supersedes the running test. The group name differs from the caller's workflow name on
36+
# purpose: a called workflow shares github.workflow with its caller, which would cancel the caller.
37+
concurrency:
38+
group: integration-test-${{ github.event_name == 'schedule' && 'schedule' || github.ref }}
39+
cancel-in-progress: true
40+
41+
env:
42+
CF_API_HOST: api.cf.127-0-0-1.nip.io
43+
CF_UAA_HOST: uaa.cf.127-0-0-1.nip.io
1244

1345
jobs:
1446
integration-test:
47+
# the cron trigger also fires on forks, where nobody wants a nightly KinD run
48+
if: ${{ github.event_name != 'schedule' || github.repository == 'cloudfoundry/cf-java-client' }}
1549
runs-on: ubuntu-latest
1650
timeout-minutes: 120
1751
strategy:
1852
fail-fast: false
1953
matrix:
20-
java: ${{ fromJSON(inputs.java-versions || '[8, 11, 17, 21]') }}
21-
name: Java ${{ matrix.java }} integration test
54+
java: ${{ fromJSON(inputs.java-versions || (github.event_name == 'schedule' && '[21]' || '[8, 11, 17, 21]')) }}
55+
name: Java ${{ matrix.java }} integration test${{ (inputs.preview || github.event_name == 'schedule') && ' (preview)' || '' }}
2256
steps:
23-
- uses: actions/checkout@v4
24-
- name: Prepare multi-module build
25-
run: git submodule update --init --recursive
57+
- uses: actions/checkout@v7
58+
with:
59+
submodules: recursive
60+
# Validation runs deploy the capi-release that matches SUPPORTED_API_VERSION. Preview runs
61+
# (scheduled, or started by hand with `preview`) keep the latest versions.
62+
- name: Resolve CAPI version
63+
id: capi
64+
if: ${{ !(inputs.preview || github.event_name == 'schedule') }}
65+
run: |
66+
# separate assignment, so that a failing resolver fails the step
67+
version="$(.github/scripts/resolve-capi-version.sh)"
68+
echo "version=$version" >> "$GITHUB_OUTPUT"
69+
# Overrides the capi chart version of kind-deployment (merged last by Helmfile).
70+
# kind-deployment's helmfile passes cloudController.appDomains, which the capi chart only
71+
# accepts from 1.241.0 on (1.240.0 fails its values schema), so older matches are raised to it.
72+
- name: Write CAPI values override
73+
id: values
74+
if: ${{ steps.capi.outputs.version != '' }}
75+
env:
76+
MIN_CAPI_VERSION: 1.241.0
77+
MATCHED_CAPI_VERSION: ${{ steps.capi.outputs.version }}
78+
run: |
79+
version="$(printf '%s\n%s\n' "$MIN_CAPI_VERSION" "$MATCHED_CAPI_VERSION" | sort -V | tail -1)"
80+
if [ "$version" != "$MATCHED_CAPI_VERSION" ]; then
81+
echo "::warning::capi-release $MATCHED_CAPI_VERSION matches SUPPORTED_API_VERSION but is too old for kind-deployment, deploying $version"
82+
fi
83+
cat > "$RUNNER_TEMP/capi-values.yaml" <<EOF
84+
charts:
85+
capi:
86+
version: $version
87+
EOF
88+
echo "file=$RUNNER_TEMP/capi-values.yaml" >> "$GITHUB_OUTPUT"
2689
# Every job gets its own Cloud Foundry on KinD, so the matrix can run in parallel.
90+
# The action deploys the kind-deployment ref given in `ref`, so keep both on the same commit.
2791
- name: Set up Cloud Foundry
28-
uses: cloudfoundry/kind-deployment/.github/actions/setup-cf@main
92+
uses: cloudfoundry/kind-deployment/.github/actions/setup-cf@8eff1a34c730d89d3c90044695cbcaba048ec68d
2993
with:
30-
github-token: ${{ secrets.GITHUB_TOKEN }}
94+
ref: 8eff1a34c730d89d3c90044695cbcaba048ec68d
95+
use-latest-versions: ${{ (inputs.preview || github.event_name == 'schedule') }}
96+
github-token: ${{ github.token }}
97+
additional-values-files: ${{ steps.values.outputs.file }}
98+
# Workaround: all Cloud Foundry components share one Postgres with the default max_connections
99+
# (100). Over a full test run it runs out ("too many clients already") and UAA, which every
100+
# Spring test context needs, stops working. kind-deployment doesn't expose the setting, and
101+
# it only takes effect after a restart.
102+
- name: Raise Postgres connection limit
103+
timeout-minutes: 20
104+
run: |
105+
.github/scripts/pg-psql.sh -c "ALTER SYSTEM SET max_connections = 500"
106+
kubectl -n cf-system rollout restart statefulset/postgresql
107+
kubectl -n cf-system rollout status statefulset/postgresql --timeout=300s
108+
.github/scripts/pg-psql.sh -tc "SHOW max_connections"
109+
# The restart leaves the components with dead pooled connections (the cloud controller
110+
# answers one 500 per stale connection and does not validate them), and some of them
111+
# crash-loop for a while. Restart them all so they reconnect, wait for the rollouts and check
112+
# that the platform answers before the tests start.
113+
kubectl -n cf-system rollout restart deployment
114+
for deployment in $(kubectl -n cf-system get deployment -o name); do
115+
kubectl -n cf-system rollout status "$deployment" --timeout=600s
116+
done
117+
for url in "https://$CF_API_HOST/v3/info" "https://$CF_UAA_HOST/info"; do
118+
curl --silent --show-error --fail --insecure --output /dev/null \
119+
--retry 60 --retry-delay 10 --retry-all-errors "$url"
120+
done
31121
- name: Set up Java
32-
uses: actions/setup-java@v3
122+
uses: actions/setup-java@v5
33123
with:
34124
distribution: liberica
35125
java-version: ${{ matrix.java }}
36126
- name: Cache Maven packages
37-
uses: actions/cache@v3
127+
uses: actions/cache@v6
38128
with:
39129
path: ~/.m2
40130
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
41131
restore-keys: ${{ runner.os }}-m2
42132
- name: Run integration tests
43133
# CC_ADMIN_PASSWORD and UAA_ADMIN_SECRET are exported by setup-cf
44134
env:
45-
TEST_APIHOST: api.cf.127-0-0-1.nip.io
135+
TEST_APIHOST: ${{ env.CF_API_HOST }}
46136
TEST_ADMIN_USERNAME: ccadmin
47137
TEST_ADMIN_PASSWORD: ${{ env.CC_ADMIN_PASSWORD }}
48138
TEST_ADMIN_CLIENTID: admin
49139
TEST_ADMIN_CLIENTSECRET: ${{ env.UAA_ADMIN_SECRET }}
50140
TEST_SKIPSSLVALIDATION: 'true'
51141
TEST_QUOTAS_ROUTES_RESERVEDPORTS: '50'
142+
# kind-deployment has no metric registrar, which the log-cache metric tests need
143+
TEST_SKIP_METRIC_REGISTRAR: 'true'
52144
run: ./mvnw -B -Pintegration-test test -Dgpg.skip
145+
# Failures like the UAA browser flows, log-cache or Docker pushes depend on the KinD setup, so
146+
# print what the cluster says about them.
147+
- name: Dump Cloud Foundry diagnostics
148+
if: ${{ failure() || cancelled() }}
149+
run: |
150+
echo "::group::postgres connections"
151+
.github/scripts/pg-psql.sh -c "SHOW max_connections" \
152+
-c "SELECT datname, usename, application_name, state, count(*) FROM pg_stat_activity GROUP BY 1,2,3,4 ORDER BY 5 DESC" || true
153+
echo "::endgroup::"
154+
kubectl get pods -A -o wide || true
155+
kubectl get events -A --sort-by=.lastTimestamp | tail -n 100 || true
156+
echo "::group::describe workload pods"
157+
kubectl -n cf-workloads describe pods || true
158+
echo "::endgroup::"
159+
for pod in $(kubectl -n cf-system get pods -o name | grep -E 'uaa|capi|cloud-controller|log-cache|loggregator|rep|diego|gorouter|metric' || true); do
160+
echo "::group::logs $pod"
161+
kubectl -n cf-system logs "$pod" --all-containers --tail=1000 || true
162+
echo "::endgroup::"
163+
done
164+
echo "::group::workload pod logs"
165+
kubectl -n cf-workloads logs --all-containers --tail=200 --prefix -l '!nonexistent' --ignore-errors || true
166+
echo "::endgroup::"

0 commit comments

Comments
 (0)