# # # The contents of this file are subject to the terms of the Common Development and # Distribution License (the License). You may not use this file except in compliance with the # License. # # You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the # specific language governing permission and limitations under the License. # # When distributing Covered Software, include this CDDL Header Notice in each file and include # the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL # Header, with the fields enclosed by brackets [] replaced by your own identifying # information: "Portions copyright [year] [name of copyright owner]". # # Copyright 2026 3A Systems, LLC. # The replication monitor of the convergence run (#1090), sourced by run.sh: how far behind # each directory server is, seen from each replication server, sampled during the load, and # the wait for the topology to converge once it stops. # One line per directory server known to each replication server - connected to it, or to a # peer of it - in the replication domain of BASE_DN: # . Both kinds of entries are named "Connected directory # server DS() ..." (DataServerHandler, LightweightServerHandler) # The replication servers are asked at once: an ldapsearch starts a JVM, which takes tens of # seconds on a runner the load keeps busy, and asked one after the other they took a sample # every minute or so instead of every five seconds sample() { local rs now dir pid pids= now=$(date +%s) dir=$(mktemp -d "$OUT/sample.XXXXXX") for rs in $RS_NODES; do admin_search $rs "$MONITOR_DOMAIN" sub "(cn=Connected directory server*)" \ server-id missing-changes approximate-delay \ | awk -v t=$now -v rs=$rs ' tolower($1) == "server-id:" { id = $2 } tolower($1) == "missing-changes:" { m = $2 } tolower($1) == "approximate-delay:" { d = $2 } /^$/ && id != "" { print t, rs, id, m, d; id = "" } END { if (id != "") print t, rs, id, m, d }' >"$dir/$rs" & pids="$pids $!" done # a replication server that does not answer only leaves its lines out, which # converged_once does not take for a zero for pid in $pids; do wait $pid || true done for rs in $RS_NODES; do cat "$dir/$rs" done rm -rf "$dir" } # Converged once every replication server knows every directory server and none of them misses # a change. An entry that is not there is not a zero: right after a server connects again its # entry is missing until the replication server has heard of it converged_once() { local s rs want s=$(sample) || return 1 want=$(echo $DS_NODES | wc -w) for rs in $RS_NODES; do [ "$(echo "$s" | awk -v rs=$rs '$2 == rs { print $3 }' | sort -u | wc -l)" -eq "$want" ] || return 1 done echo "$s" | awk '$4 != 0 { behind = 1 } END { exit behind }' } # A replication server computes missing-changes once a monitoring-period (MonitoringPublisher), # which this run leaves at its default, and a directory server its last computation does not # know reads as 0: samples that do not span a period may all read one computation, made while # changes were on their way MONITORING_PERIOD=60 # Three converged samples in a row, the last one started more than a monitoring period (and # the publisher's 5 s latch) after the first ended. Sets CONVERGED_IN to the seconds until the # first of them wait_converged() { local start=$SECONDS hits=0 first= last= until [ $hits -ge 3 ] && [ $((last - first)) -gt $((MONITORING_PERIOD + 5)) ]; do last=$SECONDS if converged_once; then hits=$((hits + 1)); first=${first:-$SECONDS}; else hits=0; first=; fi if [ $((SECONDS - start)) -ge "$QUIESCE_TIMEOUT" ]; then echo "::group::the monitor when the wait timed out (epoch, replication server, server id, missing changes, delay)" sample || true echo "::endgroup::" fail "the topology did not converge within $QUIESCE_TIMEOUT s" fi sleep 5 done CONVERGED_IN=$((first - start)) } # Samples every 5 s in the background into $OUT/samples.txt, for the peaks of the run; a # sample that fails - a server restarting - is only a gap sampler_start() { ( while :; do sample >>"$OUT/samples.txt" 2>/dev/null || true; sleep 5; done ) & SAMPLER=$! } sampler_stop() { if [ -n "${SAMPLER:-}" ]; then # waited for, so that the shell does not report the job it killed kill $SAMPLER 2>/dev/null || true wait $SAMPLER 2>/dev/null || true # a sample takes tens of seconds under the load, so the kill nearly always cuts one short # before it removes its directory, which would then be uploaded with the run. The kill # reaches the sampler only, not the searches it started: one that opens its file in the # directory after rm read it makes rm fail, which must not fail the run rm -rf "$OUT"/sample.* || true fi SAMPLER= }