If the network correction is known for both the request and response, and their sum is not larger that the measured peer delay, allowing the transparent clocks to be running up to 100 ppm faster than the client's clock, apply the corrections to the NTP offset and peer delay. Don't correct the root delay to not change the estimated maximum error.
539 lines
14 KiB
Text
539 lines
14 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_PATH:-clknetsim}
|
|
|
|
if [ ! -x $CLKNETSIM_PATH/clknetsim ]; then
|
|
echo "SKIP (clknetsim not found)"
|
|
exit 9
|
|
fi
|
|
|
|
. $CLKNETSIM_PATH/clknetsim.bash
|
|
|
|
# Default test testings
|
|
|
|
default_limit=10000
|
|
default_primary_time_offset=0.0
|
|
default_time_offset=1e-1
|
|
default_freq_offset=1e-4
|
|
default_base_delay=1e-4
|
|
default_delay_correction=""
|
|
default_jitter=1e-4
|
|
default_jitter_asymmetry=0.0
|
|
default_wander=1e-9
|
|
default_refclock_jitter=""
|
|
default_refclock_offset=0.0
|
|
|
|
default_update_interval=0
|
|
default_shift_pll=2
|
|
|
|
default_server_strata=1
|
|
default_servers=1
|
|
default_clients=1
|
|
default_peers=0
|
|
default_falsetickers=0
|
|
default_server_start=0.0
|
|
default_client_start=0.0
|
|
default_chronyc_start=1000.0
|
|
default_server_step=""
|
|
default_client_step=""
|
|
|
|
default_client_server_conf=""
|
|
default_server_server_options=""
|
|
default_client_server_options=""
|
|
default_server_peer_options=""
|
|
default_server_lpeer_options=""
|
|
default_server_rpeer_options=""
|
|
default_client_peer_options=""
|
|
default_client_lpeer_options=""
|
|
default_client_rpeer_options=""
|
|
default_server_conf=""
|
|
default_client_conf=""
|
|
default_chronyc_conf=""
|
|
default_server_chronyd_options=""
|
|
default_client_chronyd_options=""
|
|
default_chronyc_options=""
|
|
|
|
default_time_max_limit=1e-3
|
|
default_freq_max_limit=5e-4
|
|
default_time_rms_limit=3e-4
|
|
default_freq_rms_limit=1e-5
|
|
default_min_sync_time=120
|
|
default_max_sync_time=210
|
|
|
|
default_client_min_mean_out_interval=0.0
|
|
default_client_max_min_out_interval=inf
|
|
|
|
default_cmdmon_unix=1
|
|
default_pcap_dumps=0
|
|
default_dns=0
|
|
|
|
# Initialize test settings from their defaults
|
|
for defoptname in ${!default_*}; do
|
|
optname=${defoptname#default_}
|
|
[ -z "${!optname}" ] && declare "$optname"="${!defoptname}"
|
|
done
|
|
|
|
test_start() {
|
|
rm -rf tmp/*
|
|
echo "Testing $@:"
|
|
|
|
check_config_h 'FEAT_NTP 1' || test_skip
|
|
}
|
|
|
|
test_pass() {
|
|
echo "PASS"
|
|
exit 0
|
|
}
|
|
|
|
test_fail() {
|
|
echo "FAIL"
|
|
exit 1
|
|
}
|
|
|
|
test_skip() {
|
|
echo "SKIP"
|
|
exit 9
|
|
}
|
|
|
|
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() {
|
|
local direction=$1 asym
|
|
|
|
if [ $jitter_asymmetry == "0.0" ]; then
|
|
asym=""
|
|
elif [ $direction = "up" ]; then
|
|
asym=$(awk "BEGIN {print 1 - 2 * $jitter_asymmetry}")
|
|
elif [ $direction = "down" ]; then
|
|
asym=$(awk "BEGIN {print 1 + 2 * $jitter_asymmetry}")
|
|
fi
|
|
echo "(+ $base_delay (* $asym $jitter (exponential)))"
|
|
}
|
|
|
|
get_refclock_expr() {
|
|
echo "(+ $refclock_offset (* $refclock_jitter (normal)))"
|
|
}
|
|
|
|
get_chronyd_nodes() {
|
|
echo $[$servers * $server_strata + $clients]
|
|
}
|
|
|
|
get_node_name() {
|
|
local index=$1
|
|
|
|
if [ $dns -ne 0 ]; then
|
|
echo "node$index.net1.clk"
|
|
else
|
|
echo "192.168.123.$index"
|
|
fi
|
|
}
|
|
|
|
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 $(get_node_name $[$servers * ($stratum - 2) + $i]) $server_server_options"
|
|
done
|
|
for i in $(seq 1 $peers); do
|
|
[ $i -eq $peer -o $i -gt $servers ] && continue
|
|
echo -n "peer $(get_node_name $[$servers * ($stratum - 1) + $i]) $server_peer_options "
|
|
[ $i -lt $peer ] && echo "$server_lpeer_options" || echo "$server_rpeer_options"
|
|
done
|
|
echo "$server_conf"
|
|
else
|
|
echo "deny"
|
|
if [ -n "$client_server_conf" ]; then
|
|
echo "$client_server_conf"
|
|
else
|
|
for i in $(seq 1 $servers); do
|
|
echo "server $(get_node_name $[$servers * ($stratum - 2) + $i]) $client_server_options"
|
|
done
|
|
fi
|
|
for i in $(seq 1 $peers); do
|
|
[ $i -eq $peer -o $i -gt $clients ] && continue
|
|
echo -n "peer $(get_node_name $[$servers * ($stratum - 1) + $i]) $client_peer_options "
|
|
[ $i -lt $peer ] && echo "$client_lpeer_options" || echo "$client_rpeer_options"
|
|
done
|
|
echo "$client_conf"
|
|
fi
|
|
}
|
|
|
|
# Check if chrony was built with specified option in config.h
|
|
check_config_h() {
|
|
local pattern=$1
|
|
grep -q "^#define $pattern" ../../config.h
|
|
}
|
|
|
|
# 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:"
|
|
|
|
grep -q 'chronyd exiting' tmp/log.$i && \
|
|
! grep -q 'Adjustment.*exceeds.*exiting' tmp/log.$i && \
|
|
! grep -q 'Assertion.*failed' tmp/log.$i && \
|
|
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 selectable 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 mean_in_interval mean_out_interval min_in_interval min_out_interval
|
|
|
|
test_message 2 1 "checking mean/min incoming/outgoing packet interval:"
|
|
|
|
for i in $(seq 1 $(get_chronyd_nodes)); do
|
|
mean_in_interval=$(get_stat 'Mean incoming packet interval' $i)
|
|
mean_out_interval=$(get_stat 'Mean outgoing packet interval' $i)
|
|
min_in_interval=$(get_stat 'Minimum incoming packet interval' $i)
|
|
min_out_interval=$(get_stat 'Minimum outgoing packet interval' $i)
|
|
|
|
test_message 3 0 "node $i: $(printf '%.2e %.2e %.2e %.2e' \
|
|
$mean_in_interval $mean_out_interval $min_in_interval $min_out_interval)"
|
|
|
|
# Check that the mean intervals are non-zero and shorter than
|
|
# limit, incoming is not longer than outgoing for stratum 1
|
|
# servers, outgoing is not longer than incoming for clients,
|
|
# and the minimum outgoing interval is not shorter than the NTP
|
|
# sampling separation or iburst interval for clients
|
|
nodes=$[$servers * $server_strata + $clients]
|
|
check_stat $mean_in_interval 0.1 inf && \
|
|
check_stat $mean_out_interval 0.1 inf && \
|
|
([ $i -gt $servers ] || \
|
|
check_stat $mean_in_interval 0.0 $mean_out_interval 10*$jitter) && \
|
|
([ $i -le $[$servers * $server_strata] ] || \
|
|
check_stat $mean_out_interval $client_min_mean_out_interval \
|
|
$mean_in_interval 10*$jitter) && \
|
|
([ $i -le $[$servers * $server_strata] ] || \
|
|
check_stat $min_out_interval \
|
|
$([ $servers -gt 1 ] && echo 0.18 || echo 1.8) \
|
|
$client_max_min_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
|
|
}
|
|
|
|
# Check the number of messages matching a pattern in the client logs
|
|
check_log_messages() {
|
|
local i count ret=0 pattern=$1 min=$2 max=$3
|
|
|
|
test_message 2 1 "checking number of messages \"$pattern\":"
|
|
|
|
for i in $(seq $[$servers * $server_strata + 1] $(get_chronyd_nodes)); do
|
|
count=$(grep "$pattern" tmp/log.$i | wc -l)
|
|
test_message 3 0 "node $i: $count"
|
|
|
|
[ "$min" -le "$count" ] && [ "$count" -le "$max" ] && \
|
|
test_ok || test_bad
|
|
[ $? -eq 0 ] || ret=1
|
|
done
|
|
|
|
return $ret
|
|
}
|
|
|
|
# Check the number of messages matching a pattern in a specified file
|
|
check_file_messages() {
|
|
local i count ret=0 pattern=$1 min=$2 max=$3
|
|
shift 3
|
|
|
|
test_message 2 1 "checking number of messages \"$pattern\":"
|
|
|
|
for i; do
|
|
count=$(grep "$pattern" tmp/$i | wc -l)
|
|
test_message 3 0 "$i: $count"
|
|
|
|
[ "$min" -le "$count" ] && [ "$count" -le "$max" ] && \
|
|
test_ok || test_bad
|
|
[ $? -eq 0 ] || ret=1
|
|
done
|
|
|
|
return $ret
|
|
}
|
|
|
|
# Check if only NTP port (123) was used
|
|
check_packet_port() {
|
|
local i ret=0 port=123
|
|
|
|
test_message 2 1 "checking port numbers in packet log:"
|
|
|
|
for i in $(seq 1 $(get_chronyd_nodes)); do
|
|
test_message 3 0 "node $i:"
|
|
|
|
grep -E -q "^([0-9e.+-]+ ){5}$port " tmp/log.packets && \
|
|
! grep -E "^[0-9e.+-]+ $i " tmp/log.packets | \
|
|
grep -E -q -v "^([0-9e.+-]+ ){5}$port " && \
|
|
test_ok || test_bad
|
|
[ $? -eq 0 ] || ret=1
|
|
done
|
|
|
|
return $ret
|
|
}
|
|
|
|
# Print test settings which differ from default value
|
|
print_nondefaults() {
|
|
local defoptname optname
|
|
|
|
test_message 2 1 "non-default settings:"
|
|
for defoptname in ${!default_*}; do
|
|
optname=${defoptname#default_}
|
|
[ "${!defoptname}" = "${!optname}" ] || \
|
|
test_message 3 1 $optname=${!optname}
|
|
done
|
|
}
|
|
|
|
run_simulation() {
|
|
local nodes=$1
|
|
|
|
test_message 2 0 "running simulation:"
|
|
|
|
start_server $nodes \
|
|
-n 2 \
|
|
-o tmp/log.offset -f tmp/log.freq -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 options
|
|
|
|
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]
|
|
|
|
export CLKNETSIM_UNIX_SUBNET=$[$cmdmon_unix != 0 ? 2 : 0]
|
|
|
|
for i in $(seq 1 $nodes); do
|
|
echo "node${i}_shift_pll = $shift_pll"
|
|
for j in $(seq 1 $nodes); do
|
|
echo "node${i}_delay${j} = $(get_delay_expr up)"
|
|
echo "node${j}_delay${i} = $(get_delay_expr down)"
|
|
if [ -n "$delay_correction" ]; then
|
|
echo "node${i}_delay_correction${j} = $delay_correction"
|
|
echo "node${j}_delay_correction${i} = $delay_correction"
|
|
fi
|
|
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:"
|
|
|
|
[ $pcap_dumps -ne 0 ] && export CLKNETSIM_PCAP_DUMP=tmp/pcap.$node
|
|
|
|
if [ $stratum -eq 1 ]; then
|
|
step=$server_step
|
|
start=$server_start
|
|
freq=""
|
|
[ $i -le $falsetickers ] &&
|
|
offset=$i.0 || offset=$primary_time_offset
|
|
options=$server_chronyd_options
|
|
elif [ $stratum -le $server_strata ]; then
|
|
step=$server_step
|
|
start=$server_start
|
|
freq=$(get_wander_expr)
|
|
offset=0.0
|
|
options=$server_chronyd_options
|
|
else
|
|
step=$client_step
|
|
start=$client_start
|
|
freq=$(get_wander_expr)
|
|
offset=$time_offset
|
|
options=$client_chronyd_options
|
|
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" "" "$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:"
|
|
|
|
[ $pcap_dumps -ne 0 ] && export CLKNETSIM_PCAP_DUMP=tmp/pcap.$node
|
|
|
|
options=$([ $dns -eq 0 ] && printf "%s" "-n")
|
|
if [ $cmdmon_unix -ne 0 ]; then
|
|
options+=" -h /clknetsim/unix/$[$node - $clients]:1"
|
|
else
|
|
options+=" -h $(get_node_name $[$node - $clients])"
|
|
fi
|
|
|
|
echo "node${node}_start = $chronyc_start" >> tmp/conf
|
|
start_client $node chronyc "$chronyc_conf" "" "$options $chronyc_options" && \
|
|
test_ok || test_error
|
|
|
|
[ $? -ne 0 ] && return 1
|
|
node=$[$node + 1]
|
|
done
|
|
|
|
run_simulation $nodes
|
|
}
|