Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to using chrony instead of ntpd #3574

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 26 additions & 17 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8205,45 +8205,54 @@ def enable(enable):
@click.pass_context
def ntp(ctx):
"""NTP server configuration tasks"""
config_db = ConfigDBConnector()
config_db.connect()
ctx.obj = {'db': config_db}
# This is checking to see if ctx.obj is a dictionary, to differentiate it
# between unit test scenario and runtime scenario.
if not isinstance(ctx.obj, dict):
config_db = ConfigDBConnector()
config_db.connect()
ctx.obj = {'db': config_db}

@ntp.command('add')
@click.argument('ntp_ip_address', metavar='<ntp_ip_address>', required=True)
@click.option('--association-type', type=click.Choice(["server", "pool"], case_sensitive=False), default="server",
help="Define the association type for this NTP server")
@click.option('--iburst', is_flag=True, help="Enable iburst for this NTP server")
@click.option('--version', type=int, help="Specify the version for this NTP server")
@click.pass_context
def add_ntp_server(ctx, ntp_ip_address):
def add_ntp_server(ctx, ntp_ip_address, association_type, iburst, version):
""" Add NTP server IP """
if ADHOC_VALIDATION:
if not clicommon.is_ipaddress(ntp_ip_address):
if not clicommon.is_ipaddress(ntp_ip_address) and association_type != "pool":
ctx.fail('Invalid IP address')
db = ValidatedConfigDBConnector(ctx.obj['db'])
ntp_servers = db.get_table("NTP_SERVER")
if ntp_ip_address in ntp_servers:
click.echo("NTP server {} is already configured".format(ntp_ip_address))
return
else:
ntp_server_options = {}
if version:
ntp_server_options['version'] = version
if association_type != "server":
ntp_server_options['association_type'] = association_type
if iburst:
ntp_server_options['iburst'] = "on"
try:
db.set_entry('NTP_SERVER', ntp_ip_address,
{'resolve_as': ntp_ip_address,
'association_type': 'server'})
db.set_entry('NTP_SERVER', ntp_ip_address, ntp_server_options)
except ValueError as e:
ctx.fail("Invalid ConfigDB. Error: {}".format(e))
click.echo("NTP server {} added to configuration".format(ntp_ip_address))
try:
click.echo("Restarting ntp-config service...")
clicommon.run_command(['systemctl', 'restart', 'ntp-config'], display_cmd=False)
click.echo("Restarting chrony service...")
clicommon.run_command(['systemctl', 'restart', 'chrony'], display_cmd=False)
except SystemExit as e:
ctx.fail("Restart service ntp-config failed with error {}".format(e))
ctx.fail("Restart service chrony failed with error {}".format(e))

@ntp.command('del')
@click.argument('ntp_ip_address', metavar='<ntp_ip_address>', required=True)
@click.pass_context
def del_ntp_server(ctx, ntp_ip_address):
""" Delete NTP server IP """
if ADHOC_VALIDATION:
if not clicommon.is_ipaddress(ntp_ip_address):
ctx.fail('Invalid IP address')
db = ValidatedConfigDBConnector(ctx.obj['db'])
ntp_servers = db.get_table("NTP_SERVER")
if ntp_ip_address in ntp_servers:
Expand All @@ -8255,10 +8264,10 @@ def del_ntp_server(ctx, ntp_ip_address):
else:
ctx.fail("NTP server {} is not configured.".format(ntp_ip_address))
try:
click.echo("Restarting ntp-config service...")
clicommon.run_command(['systemctl', 'restart', 'ntp-config'], display_cmd=False)
click.echo("Restarting chrony service...")
clicommon.run_command(['systemctl', 'restart', 'chrony'], display_cmd=False)
except SystemExit as e:
ctx.fail("Restart service ntp-config failed with error {}".format(e))
ctx.fail("Restart service chrony failed with error {}".format(e))

#
# 'sflow' group ('config sflow ...')
Expand Down
6 changes: 0 additions & 6 deletions scripts/fast-reboot
Original file line number Diff line number Diff line change
Expand Up @@ -901,12 +901,6 @@ sync
sleep 1
sync

# sync the current system time to CMOS
if [ -x /sbin/hwclock ]; then
/sbin/hwclock -w || /bin/true
fi


if [[ -x ${DEVPATH}/${PLATFORM}/${PLATFORM_FWUTIL_AU_REBOOT_HANDLE} ]]; then
debug "Handling task file for boot type ${REBOOT_TYPE}"
${DEVPATH}/${PLATFORM}/${PLATFORM_FWUTIL_AU_REBOOT_HANDLE} ${REBOOT_TYPE} || PLATFORM_FW_AU_RC=$?
Expand Down
5 changes: 0 additions & 5 deletions scripts/reboot
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,6 @@ sync
/sbin/fstrim -av
sleep 3

# sync the current system time to CMOS
if [ -x /sbin/hwclock ]; then
/sbin/hwclock -w || /bin/true
fi

if [[ -x ${DEVPATH}/${PLATFORM}/${PLATFORM_FWUTIL_AU_REBOOT_HANDLE} ]]; then
debug "Handling task file for boot type ${REBOOT_TYPE}"
${DEVPATH}/${PLATFORM}/${PLATFORM_FWUTIL_AU_REBOOT_HANDLE} ${REBOOT_TYPE} || PLATFORM_FW_AU_RC=$?
Expand Down
5 changes: 0 additions & 5 deletions scripts/soft-reboot
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,6 @@ sync
sleep 3
sync

# sync the current system time to CMOS
if [ -x /sbin/hwclock ]; then
/sbin/hwclock -w || /bin/true
fi

if [ -x ${DEVPATH}/${PLATFORM}/${SSD_FW_UPDATE} ]; then
debug "updating ssd fw for${REBOOT_TYPE}"
${DEVPATH}/${PLATFORM}/${SSD_FW_UPDATE} ${REBOOT_TYPE}
Expand Down
23 changes: 8 additions & 15 deletions show/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1826,7 +1826,7 @@ def ntp(verbose):
"""Show NTP running configuration"""
ntp_servers = []
ntp_dict = {}
with open("/etc/ntpsec/ntp.conf") as ntp_file:
with open("/etc/chrony/chrony.conf") as ntp_file:
data = ntp_file.readlines()
for line in data:
if line.startswith("server "):
Expand Down Expand Up @@ -2076,22 +2076,15 @@ def bgp(verbose):
@click.option('--verbose', is_flag=True, help="Enable verbose output")
def ntp(ctx, verbose):
"""Show NTP information"""
from pkg_resources import parse_version
ntpstat_cmd = ["ntpstat"]
ntpcmd = ["ntpq", "-p", "-n"]
chronyc_tracking_cmd = ["chronyc", "tracking"]
chronyc_sources_cmd = ["chronyc", "sources"]
if is_mgmt_vrf_enabled(ctx) is True:
#ManagementVRF is enabled. Call ntpq using "ip vrf exec" or cgexec based on linux version
os_info = os.uname()
release = os_info[2].split('-')
if parse_version(release[0]) > parse_version("4.9.0"):
ntpstat_cmd = ['sudo', 'ip', 'vrf', 'exec', 'mgmt', 'ntpstat']
ntpcmd = ['sudo', 'ip', 'vrf', 'exec', 'mgmt', 'ntpq', '-p', '-n']
else:
ntpstat_cmd = ['sudo', 'cgexec', '-g', 'l3mdev:mgmt', 'ntpstat']
ntpcmd = ['sudo', 'cgexec', '-g', 'l3mdev:mgmt', 'ntpq', '-p', '-n']
# ManagementVRF is enabled. Call chronyc using "ip vrf exec" based on linux version
chronyc_tracking_cmd = ["sudo", "ip", "vrf", "exec", "mgmt"] + chronyc_tracking_cmd
chronyc_sources_cmd = ["sudo", "ip", "vrf", "exec", "mgmt"] + chronyc_sources_cmd

run_command(ntpstat_cmd, display_cmd=verbose)
run_command(ntpcmd, display_cmd=verbose)
run_command(chronyc_tracking_cmd, display_cmd=verbose)
run_command(chronyc_sources_cmd, display_cmd=verbose)

#
# 'uptime' command ("show uptime")
Expand Down
67 changes: 67 additions & 0 deletions tests/chrony.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
###############################################################################
# This file was AUTOMATICALLY GENERATED. DO NOT MODIFY.
# Controlled by chrony-config.sh
###############################################################################

# Welcome to the chrony configuration file. See chrony.conf(5) for more
# information about usable directives.

# Include configuration files found in /etc/chrony/conf.d.
confdir /etc/chrony/conf.d

server 10.1.1.1 iburst

server 10.22.1.12




bindacqdevice eth0

# Use time sources from DHCP.
sourcedir /run/chrony-dhcp

# Use NTP sources found in /etc/chrony/sources.d.
sourcedir /etc/chrony/sources.d


# This directive specify the file into which chronyd will store the rate
# information.
driftfile /var/lib/chrony/chrony.drift

# Save NTS keys and cookies.
ntsdumpdir /var/lib/chrony

# Uncomment the following line to turn logging on.
#log tracking measurements statistics

# Log files location.
logdir /var/log/chrony

# Stop bad estimates upsetting machine clock.
maxupdateskew 100.0

# This directive enables kernel synchronisation (every 11 minutes) of the
# real-time clock. Note that it can’t be used along with the 'rtcfile' directive.
#rtcsync

# Instead of having the kernel manage the real-time clock, have chrony do this
# instead. The reason for this is that if the system time and the real-time clock
# are signficantly different from the actual time, then the system time must be
# slewed, while the real-time clock can be stepped to the actual time. That way,
# when the device next reboots (whether it be cold, warm, or fast), it will come
# up with the actual time from the real-time clock.
rtcfile /var/lib/chrony/rtc
hwclockfile /etc/adjtime
rtcautotrim 15

# Step the system clock instead of slewing it if the adjustment is larger than
# one second, but only in the first three clock updates.
#
# Disabled because we don't want chrony to do any clock steps; it should only slew
#makestep 1 3

# Get TAI-UTC offset and leap seconds from the system tz database.
# This directive must be commented out when using time sources serving
# leap-smeared time.
leapsectz right/UTC
133 changes: 125 additions & 8 deletions tests/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3075,13 +3075,81 @@ def setup_class(cls):
import config.main
importlib.reload(config.main)

@patch("config.validated_config_db_connector.ValidatedConfigDBConnector.validated_set_entry", mock.Mock(side_effect=ValueError))
@patch('utilities_common.cli.run_command')
def test_add_ntp_server(self, mock_run_command):
config.ADHOC_VALIDATION = True
runner = CliRunner()
db = Db()
obj = {'db': db.cfgdb}

result = runner.invoke(config.config.commands["ntp"], ["add", "10.10.10.4"], obj=obj)
print(result.exit_code)
print(result.output)
assert result.exit_code == 0
mock_run_command.assert_called_once_with(['systemctl', 'restart', 'chrony'], display_cmd=False)

@patch('utilities_common.cli.run_command')
def test_add_ntp_server_version_3(self, mock_run_command):
config.ADHOC_VALIDATION = True
runner = CliRunner()
db = Db()
obj = {'db': db.cfgdb}

result = runner.invoke(config.config.commands["ntp"], ["add", "--version", "3", "10.10.10.4"], obj=obj)
print(result.exit_code)
print(result.output)
assert result.exit_code == 0
mock_run_command.assert_called_once_with(['systemctl', 'restart', 'chrony'], display_cmd=False)

@patch('utilities_common.cli.run_command')
def test_add_ntp_server_with_iburst(self, mock_run_command):
config.ADHOC_VALIDATION = True
runner = CliRunner()
db = Db()
obj = {'db': db.cfgdb}

result = runner.invoke(config.config.commands["ntp"], ["add", "--iburst", "10.10.10.4"], obj=obj)
print(result.exit_code)
print(result.output)
assert result.exit_code == 0
mock_run_command.assert_called_once_with(['systemctl', 'restart', 'chrony'], display_cmd=False)

@patch('utilities_common.cli.run_command')
def test_add_ntp_server_with_server_association(self, mock_run_command):
config.ADHOC_VALIDATION = True
runner = CliRunner()
db = Db()
obj = {'db': db.cfgdb}

result = runner.invoke(config.config.commands["ntp"], ["add", "--association-type", "server", "10.10.10.4"],
obj=obj)
print(result.exit_code)
print(result.output)
assert result.exit_code == 0
mock_run_command.assert_called_once_with(['systemctl', 'restart', 'chrony'], display_cmd=False)

@patch('utilities_common.cli.run_command')
def test_add_ntp_server_with_pool_association(self, mock_run_command):
config.ADHOC_VALIDATION = True
runner = CliRunner()
db = Db()
obj = {'db': db.cfgdb}

result = runner.invoke(config.config.commands["ntp"], ["add", "--association-type", "pool", "pool.ntp.org"],
obj=obj)
print(result.exit_code)
print(result.output)
assert result.exit_code == 0
mock_run_command.assert_called_once_with(['systemctl', 'restart', 'chrony'], display_cmd=False)

@patch("config.validated_config_db_connector.ValidatedConfigDBConnector.validated_set_entry",
mock.Mock(side_effect=ValueError))
@patch("validated_config_db_connector.device_info.is_yang_config_validation_enabled", mock.Mock(return_value=True))
def test_add_ntp_server_failed_yang_validation(self):
config.ADHOC_VALIDATION = False
runner = CliRunner()
db = Db()
obj = {'db':db.cfgdb}
obj = {'db': db.cfgdb}

result = runner.invoke(config.config.commands["ntp"], ["add", "10.10.10.x"], obj=obj)
print(result.exit_code)
Expand All @@ -3092,23 +3160,72 @@ def test_add_ntp_server_invalid_ip(self):
config.ADHOC_VALIDATION = True
runner = CliRunner()
db = Db()
obj = {'db':db.cfgdb}
obj = {'db': db.cfgdb}

result = runner.invoke(config.config.commands["ntp"], ["add", "10.10.10.x"], obj=obj)
print(result.exit_code)
print(result.output)
assert "Invalid IP address" in result.output

def test_del_ntp_server_invalid_ip(self):
@patch('utilities_common.cli.run_command')
def test_add_ntp_server_twice(self, mock_run_command):
config.ADHOC_VALIDATION = True
runner = CliRunner()
db = Db()
obj = {'db':db.cfgdb}
obj = {'db': db.cfgdb}

db.cfgdb.set_entry("NTP_SERVER", "10.10.10.4", {})

result = runner.invoke(config.config.commands["ntp"], ["del", "10.10.10.x"], obj=obj)
result = runner.invoke(config.config.commands["ntp"], ["add", "10.10.10.4"], obj=obj)
print(result.exit_code)
print(result.output)
assert "Invalid IP address" in result.output
assert result.exit_code == 0
assert "is already configured" in result.output
mock_run_command.assert_not_called()

@patch('utilities_common.cli.run_command')
def test_del_ntp_server(self, mock_run_command):
config.ADHOC_VALIDATION = True
runner = CliRunner()
db = Db()
obj = {'db': db.cfgdb}

db.cfgdb.set_entry("NTP_SERVER", "10.10.10.4", {})

result = runner.invoke(config.config.commands["ntp"], ["del", "10.10.10.4"], obj=obj)
print(result.exit_code)
print(result.output)
assert result.exit_code == 0
mock_run_command.assert_called_once_with(['systemctl', 'restart', 'chrony'], display_cmd=False)

@patch('utilities_common.cli.run_command')
def test_del_pool_ntp_server(self, mock_run_command):
config.ADHOC_VALIDATION = True
runner = CliRunner()
db = Db()
obj = {'db': db.cfgdb}

db.cfgdb.set_entry("NTP_SERVER", "pool.ntp.org", {})

result = runner.invoke(config.config.commands["ntp"], ["del", "pool.ntp.org"], obj=obj)
print(result.exit_code)
print(result.output)
assert result.exit_code == 0
mock_run_command.assert_called_once_with(['systemctl', 'restart', 'chrony'], display_cmd=False)

@patch('utilities_common.cli.run_command')
def test_del_ntp_server_not_configured(self, mock_run_command):
config.ADHOC_VALIDATION = True
runner = CliRunner()
db = Db()
obj = {'db': db.cfgdb}

result = runner.invoke(config.config.commands["ntp"], ["del", "10.10.10.4"], obj=obj)
print(result.exit_code)
print(result.output)
assert result.exit_code != 0
assert "not configured" in result.output
mock_run_command.assert_not_called()

@patch("config.main.ConfigDBConnector.get_table", mock.Mock(return_value="10.10.10.10"))
@patch("config.validated_config_db_connector.ValidatedConfigDBConnector.validated_set_entry", mock.Mock(side_effect=JsonPatchConflict))
Expand All @@ -3117,7 +3234,7 @@ def test_del_ntp_server_invalid_ip_yang_validation(self):
config.ADHOC_VALIDATION = False
runner = CliRunner()
db = Db()
obj = {'db':db.cfgdb}
obj = {'db': db.cfgdb}

result = runner.invoke(config.config.commands["ntp"], ["del", "10.10.10.10"], obj=obj)
print(result.exit_code)
Expand Down
Loading
Loading