From 7925442d32b631630b78e214cb1d2a9d71bb920a Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Tue, 13 Oct 2026 09:06:13 +0000
Subject: [PATCH] [#1178] Give the background join of the Docker image a server role, keep a failed bootstrap or upgrade unhealthy, and test the upgrade from a released image (#1184)
---
.github/workflows/build.yml | 194 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 194 insertions(+), 0 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 879a50b..0319761 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -793,6 +793,103 @@
# the password never shows on a command line: sample every process's for two probe intervals
docker exec test_health_bind sh -c 'end=$(($(date +%s) + 12)); while [ "$(date +%s)" -lt "$end" ]; do for f in /proc/[0-9]*/cmdline; do tr "\0" " " < "$f" 2>/dev/null; echo; done | grep -q "[h]c_secret_1092" && exit 1; sleep 0.2; done; exit 0'
docker rm -f test_health_bind
+ - name: Docker test failed bootstrap
+ # a start over a volume whose bootstrap failed once setup had written its
+ # configuration must not report healthy either (#1182)
+ shell: bash
+ run: |
+ IMAGE=localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}
+ MEMORY=512m
+ trap 'code=$?; echo "::group::container logs (test_failed)"; docker logs test_failed 2>&1 || true; docker inspect --format="{{json .State.Health}}" test_failed 2>&1 || true; echo "::endgroup::"; docker rm -f test_failed >/dev/null 2>&1 || true; docker volume rm test_failed_data >/dev/null 2>&1 || true; exit $code' ERR
+ # an unknown backend type fails create-backend once setup has written data/config
+ docker run -d --memory="$MEMORY" --health-interval=5s -e ADD_BASE_ENTRY="--addBaseEntry" -e BACKEND_TYPE=bogus -v test_failed_data:/opt/opendj/data --name=test_failed "$IMAGE"
+ timeout 3m bash -c 'until docker logs test_failed 2>&1 | grep -qF "failed, this container will not report itself healthy"; do sleep 2; done'
+ docker restart -t 60 test_failed
+ timeout 3m bash -c 'until docker logs test_failed 2>&1 | grep -qF "The bootstrap of this volume did not complete"; do sleep 2; done'
+ # the server is up to be looked at, and no probe passes over 45 s of probes every 5 s
+ timeout 3m bash -c 'until docker exec test_failed /opt/opendj/bin/ldapsearch --hostname localhost --port 1636 --useSsl --trustAll --bindDN "cn=Directory Manager" --bindPassword password --baseDN "" --searchScope base "(objectClass=*)" 1.1 >/dev/null 2>&1; do sleep 5; done'
+ end=$((SECONDS + 45))
+ while [ $SECONDS -lt $end ]; do
+ test "$(docker inspect --format='{{.State.Health.Status}}' test_failed)" != healthy
+ sleep 2
+ done
+ # the setup finished by hand and the mark removed, the next start reports healthy
+ docker exec test_failed /opt/opendj/bin/dsconfig create-backend --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=userRoot --type je --set=base-dn:dc=example,dc=com --set=enabled:true --no-prompt --trustAll
+ docker exec test_failed rm /opt/opendj/data/.bootstrap-pending
+ docker restart -t 60 test_failed
+ timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test_failed | grep -q \"healthy\"; do sleep 5; done'
+ docker rm -f test_failed
+ docker volume rm test_failed_data
+ - name: Docker test upgrade from a released image
+ # the build image started over a volume that the latest release bootstrapped
+ # upgrades it on start, the road the Installation Guide documents (#1183), and an
+ # upgrade that did not complete keeps it unhealthy on later starts too (#1185)
+ shell: bash
+ run: |
+ IMAGE=localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}
+ RELEASED=openidentityplatform/opendj:${{ env.release_version }}
+ MEMORY=512m
+ trap 'code=$?; echo "::group::container logs (test_upgrade)"; docker logs test_upgrade 2>&1 || true; docker inspect --format="{{json .State.Health}}" test_upgrade 2>&1 || true; echo "::endgroup::"; docker rm -f test_upgrade >/dev/null 2>&1 || true; docker volume rm test_upgrade_data >/dev/null 2>&1 || true; exit $code' ERR
+ search() { docker exec test_upgrade /opt/opendj/bin/ldapsearch --hostname localhost --port 1636 --useSsl --trustAll --bindDN "cn=Directory Manager" --bindPassword password "$@"; }
+ # release.yml publishes the GitHub release before it pushes the images of it: in between,
+ # and while a failed push stays unfixed, the latest release has no image to start from.
+ # Only that skips the test - a tag the registry does not have, which the classic image
+ # store reports as "manifest unknown" and the containerd one as a reference it could not
+ # resolve, "not found"; a pull that failed otherwise - a rate limit, the registry or the
+ # network down - fails it rather than passing it untested
+ if ! out=$(docker pull "$RELEASED" 2>&1); then
+ echo "$out"
+ case $out in
+ *"manifest unknown"*|*"failed to resolve reference"*": not found"*) echo "::warning::$RELEASED is not on Docker Hub yet, the upgrade test is skipped"; exit 0 ;;
+ *) exit 1 ;;
+ esac
+ fi
+ docker run -d --memory="$MEMORY" -e ADD_BASE_ENTRY="--addBaseEntry" -e SAMPLE_DATA=10 -v test_upgrade_data:/opt/opendj/data --name=test_upgrade "$RELEASED"
+ # the entries, not the health status: the health check of 5.1.2 turned healthy before
+ # import-ldif had finished
+ export -f search
+ timeout 5m bash -c 'until search --baseDN "ou=People,dc=example,dc=com" --searchScope one "(uid=user.9)" 1.1 2>/dev/null | grep -q "^dn:"; do sleep 5; done'
+ docker stop -t 60 test_upgrade
+ docker rm test_upgrade
+ docker run -d --memory="$MEMORY" -v test_upgrade_data:/opt/opendj/data --name=test_upgrade "$IMAGE"
+ timeout 10m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test_upgrade | grep -q \"healthy\"; do sleep 5; done'
+ # the upgrade ran its tasks: on equal versions it would be a no-op
+ if docker logs test_upgrade 2>&1 | grep -F "has already been upgraded"; then echo "::error::The build image found nothing to upgrade on the volume of $RELEASED"; false; fi
+ test "$(search --baseDN "ou=People,dc=example,dc=com" --searchScope one "(uid=user.*)" 1.1 | grep -c "^dn:")" = 10
+ # a successful upgrade leaves no mark behind (#1185)
+ docker exec test_upgrade test ! -e /opt/opendj/data/.upgrade-pending
+ # an upgrade that recorded the new version but did not complete its post-upgrade tasks:
+ # the next run of upgrade finds nothing to do, and the mark keeps the container unhealthy
+ # until the operator removes it
+ docker stop -t 60 test_upgrade
+ docker rm test_upgrade
+ docker run --rm --entrypoint sh -v test_upgrade_data:/opt/opendj/data "$IMAGE" -c 'echo "an earlier version to this one" >/opt/opendj/data/.upgrade-pending'
+ docker run -d --memory="$MEMORY" --health-interval=5s -v test_upgrade_data:/opt/opendj/data --name=test_upgrade "$IMAGE"
+ timeout 3m bash -c 'until docker logs test_upgrade 2>&1 | grep -qF "did not complete on an earlier start"; do sleep 2; done'
+ # the server is up to be looked at, and no probe passes over 45 s of probes every 5 s
+ timeout 3m bash -c 'until search --baseDN "" --searchScope base "(objectClass=*)" 1.1 >/dev/null 2>&1; do sleep 5; done'
+ end=$((SECONDS + 45))
+ while [ $SECONDS -lt $end ]; do
+ test "$(docker inspect --format='{{.State.Health.Status}}' test_upgrade)" != healthy
+ sleep 2
+ done
+ docker exec test_upgrade rm /opt/opendj/data/.upgrade-pending
+ docker restart -t 60 test_upgrade
+ timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test_upgrade | grep -q \"healthy\"; do sleep 5; done'
+ docker rm -f test_upgrade
+ docker volume rm test_upgrade_data
+ # the mark goes on before the upgrade and stays when it fails: a read-only buildinfo
+ # fails it where it records the new version, after its upgrade tasks
+ docker run -d --memory="$MEMORY" -e ADD_BASE_ENTRY="--addBaseEntry" -v test_upgrade_data:/opt/opendj/data --name=test_upgrade "$RELEASED"
+ timeout 5m bash -c 'until search --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1 2>/dev/null | grep -q "^dn:"; do sleep 5; done'
+ docker stop -t 60 test_upgrade
+ docker rm test_upgrade
+ docker run --rm --user root --entrypoint chmod -v test_upgrade_data:/opt/opendj/data "$IMAGE" 444 /opt/opendj/data/config/buildinfo
+ docker run -d --memory="$MEMORY" -v test_upgrade_data:/opt/opendj/data --name=test_upgrade "$IMAGE"
+ timeout 5m bash -c 'until docker logs test_upgrade 2>&1 | grep -qF "The upgrade did not complete, this container will not report itself healthy"; do sleep 2; done'
+ docker run --rm --entrypoint test -v test_upgrade_data:/opt/opendj/data "$IMAGE" -s /opt/opendj/data/.upgrade-pending
+ docker rm -f test_upgrade
+ docker volume rm test_upgrade_data
- name: Scan image for vulnerabilities (Trivy)
# trivy resolves the image from the local Docker daemon, so only the runner's
# linux/amd64 manifest is scanned; cache: false keeps the ~1GB trivy DBs from
@@ -1187,6 +1284,103 @@
# the password never shows on a command line: sample every process's for two probe intervals
docker exec test_health_bind sh -c 'end=$(($(date +%s) + 12)); while [ "$(date +%s)" -lt "$end" ]; do for f in /proc/[0-9]*/cmdline; do tr "\0" " " < "$f" 2>/dev/null; echo; done | grep -q "[h]c_secret_1092" && exit 1; sleep 0.2; done; exit 0'
docker rm -f test_health_bind
+ - name: Docker test failed bootstrap
+ # a start over a volume whose bootstrap failed once setup had written its
+ # configuration must not report healthy either (#1182)
+ shell: bash
+ run: |
+ IMAGE=localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}-alpine
+ MEMORY=1g
+ trap 'code=$?; echo "::group::container logs (test_failed)"; docker logs test_failed 2>&1 || true; docker inspect --format="{{json .State.Health}}" test_failed 2>&1 || true; echo "::endgroup::"; docker rm -f test_failed >/dev/null 2>&1 || true; docker volume rm test_failed_data >/dev/null 2>&1 || true; exit $code' ERR
+ # an unknown backend type fails create-backend once setup has written data/config
+ docker run -d --memory="$MEMORY" --health-interval=5s -e ADD_BASE_ENTRY="--addBaseEntry" -e BACKEND_TYPE=bogus -v test_failed_data:/opt/opendj/data --name=test_failed "$IMAGE"
+ timeout 3m bash -c 'until docker logs test_failed 2>&1 | grep -qF "failed, this container will not report itself healthy"; do sleep 2; done'
+ docker restart -t 60 test_failed
+ timeout 3m bash -c 'until docker logs test_failed 2>&1 | grep -qF "The bootstrap of this volume did not complete"; do sleep 2; done'
+ # the server is up to be looked at, and no probe passes over 45 s of probes every 5 s
+ timeout 3m bash -c 'until docker exec test_failed /opt/opendj/bin/ldapsearch --hostname localhost --port 1636 --useSsl --trustAll --bindDN "cn=Directory Manager" --bindPassword password --baseDN "" --searchScope base "(objectClass=*)" 1.1 >/dev/null 2>&1; do sleep 5; done'
+ end=$((SECONDS + 45))
+ while [ $SECONDS -lt $end ]; do
+ test "$(docker inspect --format='{{.State.Health.Status}}' test_failed)" != healthy
+ sleep 2
+ done
+ # the setup finished by hand and the mark removed, the next start reports healthy
+ docker exec test_failed /opt/opendj/bin/dsconfig create-backend --hostname localhost --port 4444 --bindDN "cn=Directory Manager" --bindPassword password --backend-name=userRoot --type je --set=base-dn:dc=example,dc=com --set=enabled:true --no-prompt --trustAll
+ docker exec test_failed rm /opt/opendj/data/.bootstrap-pending
+ docker restart -t 60 test_failed
+ timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test_failed | grep -q \"healthy\"; do sleep 5; done'
+ docker rm -f test_failed
+ docker volume rm test_failed_data
+ - name: Docker test upgrade from a released image
+ # the build image started over a volume that the latest release bootstrapped
+ # upgrades it on start, the road the Installation Guide documents (#1183), and an
+ # upgrade that did not complete keeps it unhealthy on later starts too (#1185)
+ shell: bash
+ run: |
+ IMAGE=localhost:5000/${GITHUB_REPOSITORY,,}:${{ env.release_version }}-alpine
+ RELEASED=openidentityplatform/opendj:${{ env.release_version }}-alpine
+ MEMORY=1g
+ trap 'code=$?; echo "::group::container logs (test_upgrade)"; docker logs test_upgrade 2>&1 || true; docker inspect --format="{{json .State.Health}}" test_upgrade 2>&1 || true; echo "::endgroup::"; docker rm -f test_upgrade >/dev/null 2>&1 || true; docker volume rm test_upgrade_data >/dev/null 2>&1 || true; exit $code' ERR
+ search() { docker exec test_upgrade /opt/opendj/bin/ldapsearch --hostname localhost --port 1636 --useSsl --trustAll --bindDN "cn=Directory Manager" --bindPassword password "$@"; }
+ # release.yml publishes the GitHub release before it pushes the images of it: in between,
+ # and while a failed push stays unfixed, the latest release has no image to start from.
+ # Only that skips the test - a tag the registry does not have, which the classic image
+ # store reports as "manifest unknown" and the containerd one as a reference it could not
+ # resolve, "not found"; a pull that failed otherwise - a rate limit, the registry or the
+ # network down - fails it rather than passing it untested
+ if ! out=$(docker pull "$RELEASED" 2>&1); then
+ echo "$out"
+ case $out in
+ *"manifest unknown"*|*"failed to resolve reference"*": not found"*) echo "::warning::$RELEASED is not on Docker Hub yet, the upgrade test is skipped"; exit 0 ;;
+ *) exit 1 ;;
+ esac
+ fi
+ docker run -d --memory="$MEMORY" -e ADD_BASE_ENTRY="--addBaseEntry" -e SAMPLE_DATA=10 -v test_upgrade_data:/opt/opendj/data --name=test_upgrade "$RELEASED"
+ # the entries, not the health status: the health check of 5.1.2 turned healthy before
+ # import-ldif had finished
+ export -f search
+ timeout 5m bash -c 'until search --baseDN "ou=People,dc=example,dc=com" --searchScope one "(uid=user.9)" 1.1 2>/dev/null | grep -q "^dn:"; do sleep 5; done'
+ docker stop -t 60 test_upgrade
+ docker rm test_upgrade
+ docker run -d --memory="$MEMORY" -v test_upgrade_data:/opt/opendj/data --name=test_upgrade "$IMAGE"
+ timeout 10m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test_upgrade | grep -q \"healthy\"; do sleep 5; done'
+ # the upgrade ran its tasks: on equal versions it would be a no-op
+ if docker logs test_upgrade 2>&1 | grep -F "has already been upgraded"; then echo "::error::The build image found nothing to upgrade on the volume of $RELEASED"; false; fi
+ test "$(search --baseDN "ou=People,dc=example,dc=com" --searchScope one "(uid=user.*)" 1.1 | grep -c "^dn:")" = 10
+ # a successful upgrade leaves no mark behind (#1185)
+ docker exec test_upgrade test ! -e /opt/opendj/data/.upgrade-pending
+ # an upgrade that recorded the new version but did not complete its post-upgrade tasks:
+ # the next run of upgrade finds nothing to do, and the mark keeps the container unhealthy
+ # until the operator removes it
+ docker stop -t 60 test_upgrade
+ docker rm test_upgrade
+ docker run --rm --entrypoint sh -v test_upgrade_data:/opt/opendj/data "$IMAGE" -c 'echo "an earlier version to this one" >/opt/opendj/data/.upgrade-pending'
+ docker run -d --memory="$MEMORY" --health-interval=5s -v test_upgrade_data:/opt/opendj/data --name=test_upgrade "$IMAGE"
+ timeout 3m bash -c 'until docker logs test_upgrade 2>&1 | grep -qF "did not complete on an earlier start"; do sleep 2; done'
+ # the server is up to be looked at, and no probe passes over 45 s of probes every 5 s
+ timeout 3m bash -c 'until search --baseDN "" --searchScope base "(objectClass=*)" 1.1 >/dev/null 2>&1; do sleep 5; done'
+ end=$((SECONDS + 45))
+ while [ $SECONDS -lt $end ]; do
+ test "$(docker inspect --format='{{.State.Health.Status}}' test_upgrade)" != healthy
+ sleep 2
+ done
+ docker exec test_upgrade rm /opt/opendj/data/.upgrade-pending
+ docker restart -t 60 test_upgrade
+ timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" test_upgrade | grep -q \"healthy\"; do sleep 5; done'
+ docker rm -f test_upgrade
+ docker volume rm test_upgrade_data
+ # the mark goes on before the upgrade and stays when it fails: a read-only buildinfo
+ # fails it where it records the new version, after its upgrade tasks
+ docker run -d --memory="$MEMORY" -e ADD_BASE_ENTRY="--addBaseEntry" -v test_upgrade_data:/opt/opendj/data --name=test_upgrade "$RELEASED"
+ timeout 5m bash -c 'until search --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1 2>/dev/null | grep -q "^dn:"; do sleep 5; done'
+ docker stop -t 60 test_upgrade
+ docker rm test_upgrade
+ docker run --rm --user root --entrypoint chmod -v test_upgrade_data:/opt/opendj/data "$IMAGE" 444 /opt/opendj/data/config/buildinfo
+ docker run -d --memory="$MEMORY" -v test_upgrade_data:/opt/opendj/data --name=test_upgrade "$IMAGE"
+ timeout 5m bash -c 'until docker logs test_upgrade 2>&1 | grep -qF "The upgrade did not complete, this container will not report itself healthy"; do sleep 2; done'
+ docker run --rm --entrypoint test -v test_upgrade_data:/opt/opendj/data "$IMAGE" -s /opt/opendj/data/.upgrade-pending
+ docker rm -f test_upgrade
+ docker volume rm test_upgrade_data
- name: Scan image for vulnerabilities (Trivy)
# trivy resolves the image from the local Docker daemon, so only the runner's
# linux/amd64 manifest is scanned; cache: false keeps the ~1GB trivy DBs from
--
Gitblit v1.10.0