chrony/test/simulation/test.common
Miroslav Lichvar 115e83f3aa Add simulation tests
Use clknetsim to run multiple chronyd instances with simulated clocks
and network. It allows fast and reproducible testing, without real
network.

Included are several tests of performance in different clock/network
conditions, chronyd options, NTP authentication, chronyc, and past bug
fixes.
2014-02-27 18:34:52 +01:00

413 lines
10 KiB
Text

# Copyright (C) 2013-2014 Miroslav Lichvar <mlichvar@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
export LC_ALL=C
export PATH=../../:$PATH
export CLKNETSIM_PATH=clknetsim
# Known working clknetsim revision
clknetsim_revision=eb0b85335c40027ac941b2276142542fff482107
clknetsim_url=https://github.com/mlichvar/clknetsim/archive/$clknetsim_revision.tar.gz
# Only Linux is supported
if [ "$(uname -s)" != Linux ]; then
echo "Simulation tests supported only on Linux"
exit 3
fi
# Try to download clknetsim if not found
if [ ! -e $CLKNETSIM_PATH ]; then
curl -L "$clknetsim_url" | tar xz || exit 3
ln -s clknetsim-$clknetsim_revision clknetsim || exit 3
fi
# Try to build clknetsim if not built
if [ ! -x $CLKNETSIM_PATH/clknetsim -o ! -e $CLKNETSIM_PATH/clknetsim.so ]; then
make -C clknetsim || exit 3
fi
. $CLKNETSIM_PATH/clknetsim.bash
# Default test testings
default_limit=10000
default_time_offset=1e-1
default_freq_offset=1e-4
default_base_delay=1e-4
default_jitter=1e-4
default_wander=1e-9
default_refclock_jitter=""
default_update_interval=0
default_log_packets=0
default_shift_pll=2
default_server_strata=1
default_servers=1
default_clients=1
default_peers=0
default_server_start=0.0
default_client_start=0.0
default_chronyc_start=1000.0
default_server_step=""
default_client_step=""
default_server_server_options=""
default_client_server_options=""
default_server_peer_options=""
default_client_peer_options=""
default_server_conf=""
default_client_conf=""
default_chronyc_conf=""
default_chronyd_options=""
default_time_max_limit=1e-3
default_freq_max_limit=5e-4
default_time_rms_limit=2e-4
default_freq_rms_limit=1e-5
default_min_sync_time=120
default_max_sync_time=210
# Initialize test settings from their defaults
for defopt in $(declare | grep '^default_'); do
defoptname=${defopt%%=*}
optname=${defoptname#default_}
eval "[ -z \"\${$optname:+a}\" ] && $optname=\"\$$defoptname\""
done
test_start() {
rm -f tmp/*
echo "Testing $@:"
}
test_pass() {
echo "PASS"
exit 0
}
test_fail() {
echo "FAIL"
exit 1
}
test_ok() {
pad_line
echo -e "\tOK"
return 0
}
test_bad() {
pad_line
echo -e "\tBAD"
return 1
}
test_error() {
pad_line
echo -e "\tERROR"
return 1
}
msg_length=0
pad_line() {
local line_length=56
[ $msg_length -lt $line_length ] && \
printf "%$[$line_length - $msg_length]s" ""
msg_length=0
}
# Print aligned message
test_message() {
local level=$1 eol=$2
shift 2
local msg="$*"
while [ $level -gt 0 ]; do
echo -n " "
level=$[$level - 1]
msg_length=$[$msg_length + 2]
done
echo -n "$msg"
msg_length=$[$msg_length + ${#msg}]
if [ $eol -ne 0 ]; then
echo
msg_length=0
fi
}
get_wander_expr() {
local scaled_wander
scaled_wander=$(awk "BEGIN {print $wander / \
sqrt($update_interval < 0 ? 2^-($update_interval) : 1)}")
echo "(+ $freq_offset (sum (* $scaled_wander (normal))))"
}
get_delay_expr() {
echo "(+ $base_delay (* $jitter (exponential)))"
}
get_refclock_expr() {
echo "(* $refclock_jitter (normal))"
}
get_chronyd_nodes() {
echo $[$servers * $server_strata + $clients]
}
get_chronyd_conf() {
local i stratum=$1 peer=$2
if [ $stratum -eq 1 ]; then
echo "local stratum 1"
echo "$server_conf"
elif [ $stratum -le $server_strata ]; then
for i in $(seq 1 $servers); do
echo "server 192.168.123.$[$servers * ($stratum - 2) + $i] $server_server_options"
done
for i in $(seq 1 $peers); do
[ $i -eq $peer -o $i -gt $servers ] && continue
echo "peer 192.168.123.$[$servers * ($stratum - 1) + $i] $server_peer_options"
done
echo "$server_conf"
else
for i in $(seq 1 $servers); do
echo "server 192.168.123.$[$servers * ($stratum - 2) + $i] $client_server_options"
done
for i in $(seq 1 $peers); do
[ $i -eq $peer -o $i -gt $clients ] && continue
echo "peer 192.168.123.$[$servers * ($stratum - 1) + $i] $client_peer_options"
done
echo "$client_conf"
fi
}
# Check if the clock was well synchronized
check_sync() {
local i sync_time max_time_error max_freq_error ret=0
local rms_time_error rms_freq_error
test_message 2 1 "checking clock sync time, max/rms time/freq error:"
for i in $(seq 1 $(get_chronyd_nodes)); do
[ $i -gt $[$servers * $server_strata] ] || continue
sync_time=$(find_sync tmp/log.offset tmp/log.freq $i \
$time_max_limit $freq_max_limit 1.0)
max_time_error=$(get_stat 'Maximum absolute offset' $i)
max_freq_error=$(get_stat 'Maximum absolute frequency' $i)
rms_time_error=$(get_stat 'RMS offset' $i)
rms_freq_error=$(get_stat 'RMS frequency' $i)
test_message 3 0 "node $i: $sync_time $(printf '%.2e %.2e %.2e %.2e' \
$max_time_error $max_freq_error $rms_time_error $rms_freq_error)"
check_stat $sync_time $min_sync_time $max_sync_time && \
check_stat $max_time_error 0.0 $time_max_limit && \
check_stat $max_freq_error 0.0 $freq_max_limit && \
check_stat $rms_time_error 0.0 $time_rms_limit && \
check_stat $rms_freq_error 0.0 $freq_rms_limit && \
test_ok || test_bad
[ $? -eq 0 ] || ret=1
done
return $ret
}
# Check if chronyd exited properly
check_chronyd_exit() {
local i ret=0
test_message 2 1 "checking chronyd exit:"
for i in $(seq 1 $(get_chronyd_nodes)); do
test_message 3 0 "node $i:"
tail -n 1 tmp/log.$i | grep -q 'chronyd exiting' && \
test_ok || test_bad
[ $? -eq 0 ] || ret=1
done
return $ret
}
# Check for problems in source selection
check_source_selection() {
local i ret=0
test_message 2 1 "checking source selection:"
for i in $(seq $[$servers * $server_strata + 1] $(get_chronyd_nodes)); do
test_message 3 0 "node $i:"
! grep -q 'no majority\|no reachable sources' tmp/log.$i && \
grep -q 'Selected source' tmp/log.$i && \
test_ok || test_bad
[ $? -eq 0 ] || ret=1
done
return $ret
}
# Check if incoming and outgoing packet intervals are sane
check_packet_interval() {
local i ret=0 in_interval out_interval
test_message 2 1 "checking incoming and outgoing packet interval:"
for i in $(seq 1 $(get_chronyd_nodes)); do
in_interval=$(get_stat 'Mean incoming packet interval' $i)
out_interval=$(get_stat 'Mean outgoing packet interval' $i)
test_message 3 0 "node $i: $(printf '%.2e %.2e' \
$in_interval $out_interval)"
# Check that the intervals are non-zero and shorter than limit,
# incoming is not longer than outgoing for stratum 1 servers, and
# outgoing is not longer than incoming for clients.
nodes=$[$servers * $server_strata + $clients]
check_stat $in_interval 0.1 $limit && \
check_stat $out_interval 0.1 $limit && \
([ $i -gt $servers ] || \
check_stat $in_interval 0.0 $out_interval) && \
([ $i -le $[$servers * $server_strata] ] || \
check_stat $in_interval 0.0 $out_interval) && \
test_ok || test_bad
[ $? -eq 0 ] || ret=1
done
return $ret
}
# Compare chronyc output with specified pattern
check_chronyc_output() {
local i ret=0 pattern=$1
test_message 2 1 "checking chronyc output:"
for i in $(seq $[$(get_chronyd_nodes) + 1] $[$(get_chronyd_nodes) + $clients]); do
test_message 3 0 "node $i:"
[[ "$(cat tmp/log.$i)" =~ $pattern ]] && \
test_ok || test_bad
[ $? -eq 0 ] || ret=1
done
return $ret
}
# Print test settings which differ from default value
print_nondefaults() {
local defopt defoptname optname
test_message 2 1 "non-default settings:"
declare | grep '^default_*' | while read defopt; do
defoptname=${defopt%%=*}
optname=${defoptname#default_}
eval "[ \"\$$optname\" = \"\$$defoptname\" ]" || \
test_message 3 1 $(eval "echo $optname=\$$optname")
done
}
run_simulation() {
local nodes=$1
test_message 2 0 "running simulation:"
start_server $nodes \
-o tmp/log.offset -f tmp/log.freq \
$([ $log_packets -ne 0 ] && echo -p tmp/log.packets) \
-R $(awk "BEGIN {print $update_interval < 0 ? 2^-($update_interval) : 1}") \
-r $(awk "BEGIN {print $max_sync_time * 2^$update_interval}") \
-l $(awk "BEGIN {print $limit * 2^$update_interval}") && test_ok || test_error
}
run_test() {
local i j n stratum node nodes step start freq offset conf
test_message 1 1 "network with $servers*$server_strata servers and $clients clients:"
print_nondefaults
nodes=$(get_chronyd_nodes)
[ -n "$chronyc_conf" ] && nodes=$[$nodes + $clients]
for i in $(seq 1 $nodes); do
echo "node${i}_shift_pll = $shift_pll"
for j in $(seq 1 $nodes); do
[ $i -eq $j ] && continue
echo "node${i}_delay${j} = $(get_delay_expr)"
echo "node${j}_delay${i} = $(get_delay_expr)"
done
done > tmp/conf
node=1
for stratum in $(seq 1 $[$server_strata + 1]); do
[ $stratum -le $server_strata ] && n=$servers || n=$clients
for i in $(seq 1 $n); do
test_message 2 0 "starting node $node:"
if [ $stratum -eq 1 ]; then
step=$server_step
start=$server_start
freq=""
offset=0.0
elif [ $stratum -le $server_strata ]; then
step=$server_step
start=$server_start
freq=$(get_wander_expr)
offset=0.0
else
step=$client_step
start=$client_start
freq=$(get_wander_expr)
offset=$time_offset
fi
conf=$(get_chronyd_conf $stratum $i $n)
[ -z "$freq" ] || echo "node${node}_freq = $freq" >> tmp/conf
[ -z "$step" ] || echo "node${node}_step = $step" >> tmp/conf
[ -z "$refclock_jitter" ] || \
echo "node${node}_refclock = $(get_refclock_expr)" >> tmp/conf
echo "node${node}_offset = $offset" >> tmp/conf
echo "node${node}_start = $start" >> tmp/conf
start_client $node chronyd "$conf" "" "$chronyd_options" && \
test_ok || test_error
[ $? -ne 0 ] && return 1
node=$[$node + 1]
done
done
for i in $(seq 1 $[$nodes - $node + 1]); do
test_message 2 0 "starting node $node:"
echo "node${node}_start = $chronyc_start" >> tmp/conf
start_client $node chronyc "$chronyc_conf" "" \
"-n -h 192.168.123.$[$node - $clients]" && \
test_ok || test_error
[ $? -ne 0 ] && return 1
node=$[$node + 1]
done
run_simulation $nodes
}