blob: 7005c9e1b937cc1d53c46c8a2df336316eb578cc [file] [log] [blame]
# Copyright (C) Miroslav Lichvar 2009
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
export LC_ALL=C
export PATH=${CHRONY_PATH:-../..}:$PATH
TEST_DIR=${TEST_DIR:-$(pwd)/tmp}
TEST_LIBDIR=${TEST_LIBDIR:-$TEST_DIR}
TEST_LOGDIR=${TEST_LOGDIR:-$TEST_DIR}
TEST_RUNDIR=${TEST_RUNDIR:-$TEST_DIR}
TEST_SCFILTER=${TEST_SCFILTER:-0}
test_start() {
check_chronyd_features NTP CMDMON || test_skip "NTP/CMDMON support disabled"
[ "${#TEST_DIR}" -ge 5 ] || test_skip "invalid TEST_DIR"
rm -rf "$TEST_DIR"
mkdir -p "$TEST_DIR" && chmod 700 "$TEST_DIR" || test_skip "could not create $TEST_DIR"
[ -d "$TEST_LIBDIR" ] || test_skip "missing $TEST_LIBDIR"
[ -d "$TEST_LOGDIR" ] || test_skip "missing $TEST_LOGDIR"
[ -d "$TEST_RUNDIR" ] || test_skip "missing $TEST_RUNDIR"
rm -f "$TEST_LIBDIR"/* "$TEST_LOGDIR"/* "$TEST_RUNDIR"/*
if [ "$user" != "root" ]; then
id -u "$user" > /dev/null 2> /dev/null || test_skip "missing user $user"
chown "$user:$(id -g "$user")" "$TEST_DIR" || test_skip "could not chown $TEST_DIR"
su "$user" -s /bin/sh -c "touch $TEST_DIR/test" 2> /dev/null || \
test_skip "$user cannot access $TEST_DIR"
rm "$TEST_DIR/test"
fi
echo "Testing $*:"
}
test_pass() {
echo "PASS"
exit 0
}
test_fail() {
echo "FAIL"
exit 1
}
test_skip() {
local msg=$1
[ -n "$msg" ] && echo "SKIP ($msg)" || 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
}
chronyd=$(command -v chronyd)
chronyc=$(command -v chronyc)
[ $EUID -eq 0 ] || test_skip "not root"
[ -x "$chronyd" ] || test_skip "chronyd not found"
[ -x "$chronyc" ] || test_skip "chronyc not found"
if netstat -aln > /dev/null 2> /dev/null; then
port_list_command="netstat -aln"
elif ss -atun > /dev/null 2> /dev/null; then
port_list_command="ss -atun"
else
test_skip "missing netstat or ss"
fi
# Default test testings
default_minimal_config=0
default_extra_chronyd_directives=""
default_extra_chronyd_options=""
default_clock_control=0
default_server=127.0.0.1
default_server_name=127.0.0.1
default_server_options=""
default_user=root
# Initialize test settings from their defaults
for defoptname in ${!default_*}; do
optname=${defoptname#default_}
[ -z "${!optname}" ] && declare "$optname"="${!defoptname}"
done
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
}
# Check if chronyd has specified features
check_chronyd_features() {
local feature features
features=$($chronyd -v | sed 's/.*(\(.*\)).*/\1/')
for feature; do
echo "$features" | grep -q "+$feature" || return 1
done
}
# Print test settings which differ from default value
print_nondefaults() {
local defoptname optname
test_message 1 1 "non-default settings:"
for defoptname in ${!default_*}; do
optname=${defoptname#default_}
[ "${!defoptname}" = "${!optname}" ] || \
test_message 2 1 "$optname"=${!optname}
done
}
get_conffile() {
echo "$TEST_DIR/chronyd.conf"
}
get_pidfile() {
echo "$TEST_RUNDIR/chronyd.pid"
}
get_logfile() {
echo "$TEST_LOGDIR/chronyd.log"
}
get_cmdsocket() {
echo "$TEST_RUNDIR/chronyd.sock"
}
# Find a free port in the 10000-20000 range (their use is racy)
get_free_port() {
local port
while true; do
port=$((RANDOM % 10000 + 10000))
$port_list_command | grep -q '^\(tcp\|udp\).*[:.]'"$port " && continue
break
done
echo $port
}
generate_chrony_conf() {
local ntpport cmdport
ntpport=$(get_free_port)
cmdport=$(get_free_port)
echo "0.0 10000" > "$TEST_LIBDIR/driftfile"
echo "1 MD5 abcdefghijklmnopq" > "$TEST_DIR/keys"
chown "$user:$(id -g "$user")" "$TEST_LIBDIR/driftfile" "$TEST_DIR/keys"
echo "0.0" > "$TEST_DIR/tempcomp"
(
echo "pidfile $(get_pidfile)"
echo "bindcmdaddress $(get_cmdsocket)"
echo "port $ntpport"
echo "cmdport $cmdport"
echo "$extra_chronyd_directives"
[ "$minimal_config" -ne 0 ] && exit 0
echo "allow"
echo "cmdallow"
echo "local"
echo "server $server_name port $ntpport minpoll -6 maxpoll -6 $server_options"
[ "$server" = "127.0.0.1" ] && echo "bindacqaddress $server"
echo "bindaddress 127.0.0.1"
echo "bindcmdaddress 127.0.0.1"
echo "dumpdir $TEST_RUNDIR"
echo "logdir $TEST_LOGDIR"
echo "log tempcomp rawmeasurements refclocks statistics tracking rtc"
echo "logbanner 0"
echo "smoothtime 100.0 0.001"
echo "leapsectz right/UTC"
echo "dscp 46"
echo "include /dev/null"
echo "keyfile $TEST_DIR/keys"
echo "driftfile $TEST_LIBDIR/driftfile"
echo "tempcomp $TEST_DIR/tempcomp 0.1 0 0 0 0"
) > "$(get_conffile)"
}
get_chronyd_options() {
[ "$clock_control" -eq 0 ] && echo "-x"
echo "-l $(get_logfile)"
echo "-f $(get_conffile)"
echo "-u $user"
echo "-F $TEST_SCFILTER"
echo "$extra_chronyd_options"
}
# Start a chronyd instance
start_chronyd() {
local pid pidfile=$(get_pidfile)
print_nondefaults
test_message 1 0 "starting chronyd"
generate_chrony_conf
trap stop_chronyd EXIT
rm -f "$TEST_LOGDIR"/*.log
$CHRONYD_WRAPPER "$chronyd" $(get_chronyd_options) > "$TEST_DIR/chronyd.out" 2>&1
[ $? -eq 0 ] && [ -f "$pidfile" ] && ps -p "$(cat "$pidfile")" > /dev/null && test_ok || test_error
}
wait_for_sync() {
local prev_length
test_message 1 0 "waiting for synchronization"
prev_length=$msg_length
for i in $(seq 1 10); do
run_chronyc "ntpdata $server" > /dev/null 2>&1 || break
if check_chronyc_output "Total RX +: [1-9]" > /dev/null 2>&1; then
msg_length=$prev_length
test_ok
return
fi
sleep 1
done
msg_length=$prev_length
test_error
}
# Stop the chronyd instance
stop_chronyd() {
local pid pidfile
pidfile=$(get_pidfile)
[ -f "$pidfile" ] || return 0
pid=$(cat "$pidfile")
test_message 1 0 "stopping chronyd"
if ! kill "$pid" 2> /dev/null; then
test_error
return
fi
# Wait for the process to terminate (we cannot use "wait")
while ps -p "$pid" > /dev/null; do
sleep 0.1
done
test_ok
}
# Check chronyd log for expected and unexpected messages
check_chronyd_messages() {
local logfile=$(get_logfile)
test_message 1 0 "checking chronyd messages"
grep -q 'chronyd exiting' "$logfile" && \
([ "$clock_control" -eq 0 ] || ! grep -q 'Disabled control of system clock' "$logfile") && \
([ "$clock_control" -ne 0 ] || grep -q 'Disabled control of system clock' "$logfile") && \
([ "$minimal_config" -ne 0 ] || grep -q 'Frequency .* read from' "$logfile") && \
grep -q 'chronyd exiting' "$logfile" && \
! grep -q 'Could not' "$logfile" && \
! grep -q 'Disabled command socket' "$logfile" && \
test_ok || test_bad
}
# Check the number of messages matching a pattern in a specified file
check_chronyd_message_count() {
local count pattern=$1 min=$2 max=$3 logfile=$(get_logfile)
test_message 1 0 "checking message \"$pattern\""
count=$(grep "$pattern" "$(get_logfile)" | wc -l)
[ "$min" -le "$count" ] && [ "$count" -le "$max" ] && test_ok || test_bad
}
# Check the logs and dump file for measurements and a clock update
check_chronyd_files() {
test_message 1 0 "checking chronyd files"
grep -q " $server .* 111 111 1110 " "$TEST_LOGDIR/measurements.log" && \
[ -f "$TEST_LOGDIR/tempcomp.log" ] && [ "$(wc -l < "$TEST_LOGDIR/tempcomp.log")" -ge 2 ] && \
test_ok || test_bad
}
# Run a chronyc command
run_chronyc() {
local host=$chronyc_host options="-n -m"
test_message 1 0 "running chronyc $([ -n "$host" ] && echo "@$host ")$*"
if [ -z "$host" ]; then
host="$(get_cmdsocket)"
else
options="$options -p $(grep cmdport "$(get_conffile)" | awk '{print $2}')"
fi
$CHRONYC_WRAPPER "$chronyc" -h "$host" $options "$@" > "$TEST_DIR/chronyc.out" && \
test_ok || test_error
}
# Compare chronyc output with specified pattern
check_chronyc_output() {
local pattern=$1
test_message 1 0 "checking chronyc output"
[[ "$(cat "$TEST_DIR/chronyc.out")" =~ $pattern ]] && test_ok || test_bad
}