Skip to content

Commit 359dfc0

Browse files
[Clock] Implement clock CLI (sonic-net#2793)
* Implement clock CLI * Add tests for clock CLI * Update Command-Reference.md Updated relevant new CLI commands for clock mgmt config clock timezone <timezone> config clock date <YYYY-MM-DD> <HH:MM:SS> show clock timezones --------- Signed-off-by: Yevhen Fastiuk <[email protected]> Co-authored-by: Meir Renford <[email protected]>
1 parent b316fc2 commit 359dfc0

File tree

5 files changed

+213
-4
lines changed

5 files changed

+213
-4
lines changed

config/main.py

+63
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/sbin/env python
22

33
import click
4+
import datetime
45
import ipaddress
56
import json
67
import jsonpatch
@@ -7103,5 +7104,67 @@ def del_subinterface(ctx, subinterface_name):
71037104
except JsonPatchConflict as e:
71047105
ctx.fail("{} is invalid vlan subinterface. Error: {}".format(subinterface_name, e))
71057106

7107+
7108+
#
7109+
# 'clock' group ('config clock ...')
7110+
#
7111+
@config.group()
7112+
def clock():
7113+
"""Configuring system clock"""
7114+
pass
7115+
7116+
7117+
def get_tzs(ctx, args, incomplete):
7118+
ret = clicommon.run_command('timedatectl list-timezones',
7119+
display_cmd=False, ignore_error=False,
7120+
return_cmd=True)
7121+
if len(ret) == 0:
7122+
return []
7123+
7124+
lst = ret[0].split('\n')
7125+
return [k for k in lst if incomplete in k]
7126+
7127+
7128+
@clock.command()
7129+
@click.argument('timezone', metavar='<timezone_name>', required=True,
7130+
autocompletion=get_tzs)
7131+
def timezone(timezone):
7132+
"""Set system timezone"""
7133+
7134+
if timezone not in get_tzs(None, None, ''):
7135+
click.echo(f'Timezone {timezone} does not conform format')
7136+
sys.exit(1)
7137+
7138+
config_db = ConfigDBConnector()
7139+
config_db.connect()
7140+
config_db.mod_entry(swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, 'localhost',
7141+
{'timezone': timezone})
7142+
7143+
7144+
@clock.command()
7145+
@click.argument('date', metavar='<YYYY-MM-DD>', required=True)
7146+
@click.argument('time', metavar='<HH:MM:SS>', required=True)
7147+
def date(date, time):
7148+
"""Set system date and time"""
7149+
valid = True
7150+
try:
7151+
datetime.datetime.strptime(date, '%Y-%m-%d')
7152+
except ValueError:
7153+
click.echo(f'Date {date} does not conform format YYYY-MM-DD')
7154+
valid = False
7155+
7156+
try:
7157+
datetime.datetime.strptime(time, '%H:%M:%S')
7158+
except ValueError:
7159+
click.echo(f'Time {time} does not conform format HH:MM:SS')
7160+
valid = False
7161+
7162+
if not valid:
7163+
sys.exit(1)
7164+
7165+
date_time = f'{date} {time}'
7166+
clicommon.run_command(['timedatectl', 'set-time', date_time])
7167+
7168+
71067169
if __name__ == '__main__':
71077170
config()

doc/Command-Reference.md

+56
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,62 @@ This command displays the current date and time configured on the system
543543
Mon Mar 25 20:25:16 UTC 2019
544544
```
545545
546+
**config clock date**
547+
548+
This command will set the date-time of the systetm, given strings with date-time format <YYYY-MM-DD> <HH:MM:SS>
549+
550+
- Usage:
551+
```
552+
config clock date <YYYY-MM-DD> <HH:MM:SS>
553+
```
554+
555+
- Parameters:
556+
- _date_: valid date in format YYYY-MM-DD
557+
- _time_: valid time in format HH:MM:SS
558+
559+
- Example:
560+
```
561+
admin@sonic:~$ config clock date 2023-04-10 13:54:36
562+
```
563+
564+
**config clock timezone**
565+
566+
This command will set the timezone of the systetm, given a string of a valid timezone.
567+
568+
- Usage:
569+
```
570+
config clock timezone <timezone>
571+
```
572+
573+
- Parameters:
574+
- _timezone_: valid timezone to be configured
575+
576+
577+
- Example:
578+
```
579+
admin@sonic:~$ config clock timezone Africa/Accra
580+
581+
582+
**show clock timezones**
583+
584+
This command Will display list of all valid timezones to be configured.
585+
586+
- Usage:
587+
```
588+
show clock timezones
589+
```
590+
591+
- Example:
592+
```
593+
root@host:~$ show clock timezones
594+
Africa/Abidjan
595+
Africa/Accra
596+
Africa/Addis_Ababa
597+
Africa/Algiers
598+
Africa/Asmara
599+
...
600+
```
601+
546602
**show boot**
547603

548604
This command displays the current OS image, the image to be loaded on next reboot, and lists all the available images installed on the device

show/main.py

+23-4
Original file line numberDiff line numberDiff line change
@@ -1771,13 +1771,32 @@ def uptime(verbose):
17711771
cmd = ['uptime', '-p']
17721772
run_command(cmd, display_cmd=verbose)
17731773

1774-
@cli.command()
1774+
1775+
#
1776+
# 'clock' command group ("show clock ...")
1777+
#
1778+
@cli.group('clock', invoke_without_command=True)
1779+
@click.pass_context
17751780
@click.option('--verbose', is_flag=True, help="Enable verbose output")
1776-
def clock(verbose):
1781+
def clock(ctx, verbose):
17771782
"""Show date and time"""
1778-
cmd = ["date"]
1779-
run_command(cmd, display_cmd=verbose)
1783+
# If invoking subcomand, no need to do anything
1784+
if ctx.invoked_subcommand is not None:
1785+
return
17801786

1787+
run_command(['date'], display_cmd=verbose)
1788+
1789+
1790+
@clock.command()
1791+
@click.option('--verbose', is_flag=True, help="Enable verbose output")
1792+
def timezones(verbose):
1793+
"""List of available timezones"""
1794+
run_command(['timedatectl', 'list-timezones'], display_cmd=verbose)
1795+
1796+
1797+
#
1798+
# 'system-memory' command ("show system-memory")
1799+
#
17811800
@cli.command('system-memory')
17821801
@click.option('--verbose', is_flag=True, help="Enable verbose output")
17831802
def system_memory(verbose):

tests/config_test.py

+62
Original file line numberDiff line numberDiff line change
@@ -2363,3 +2363,65 @@ def test_fec(self, mock_run_command):
23632363
def teardown(self):
23642364
print("TEARDOWN")
23652365

2366+
2367+
class TestConfigClock(object):
2368+
timezone_test_val = ['Europe/Kyiv', 'Asia/Israel', 'UTC']
2369+
2370+
@classmethod
2371+
def setup_class(cls):
2372+
print('SETUP')
2373+
import config.main
2374+
importlib.reload(config.main)
2375+
2376+
@patch('config.main.get_tzs', mock.Mock(return_value=timezone_test_val))
2377+
def test_timezone_good(self):
2378+
runner = CliRunner()
2379+
obj = {'db': Db().cfgdb}
2380+
2381+
result = runner.invoke(
2382+
config.config.commands['clock'].commands['timezone'],
2383+
['UTC'], obj=obj)
2384+
2385+
assert result.exit_code == 0
2386+
2387+
@patch('config.main.get_tzs', mock.Mock(return_value=timezone_test_val))
2388+
def test_timezone_bad(self):
2389+
runner = CliRunner()
2390+
obj = {'db': Db().cfgdb}
2391+
2392+
result = runner.invoke(
2393+
config.config.commands['clock'].commands['timezone'],
2394+
['Atlantis'], obj=obj)
2395+
2396+
assert result.exit_code != 0
2397+
assert 'Timezone Atlantis does not conform format' in result.output
2398+
2399+
@patch('utilities_common.cli.run_command',
2400+
mock.MagicMock(side_effect=mock_run_command_side_effect))
2401+
def test_date_good(self):
2402+
runner = CliRunner()
2403+
obj = {'db': Db().cfgdb}
2404+
2405+
result = runner.invoke(
2406+
config.config.commands['clock'].commands['date'],
2407+
['2020-10-10', '10:20:30'], obj=obj)
2408+
2409+
assert result.exit_code == 0
2410+
2411+
@patch('utilities_common.cli.run_command',
2412+
mock.MagicMock(side_effect=mock_run_command_side_effect))
2413+
def test_date_bad(self):
2414+
runner = CliRunner()
2415+
obj = {'db': Db().cfgdb}
2416+
2417+
result = runner.invoke(
2418+
config.config.commands['clock'].commands['date'],
2419+
['20-10-10', '60:70:80'], obj=obj)
2420+
2421+
assert result.exit_code != 0
2422+
assert 'Date 20-10-10 does not conform format' in result.output
2423+
assert 'Time 60:70:80 does not conform format' in result.output
2424+
2425+
@classmethod
2426+
def teardown_class(cls):
2427+
print('TEARDOWN')

tests/show_test.py

+9
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,15 @@ def test_show_clock(self, mock_run_command):
926926
assert result.exit_code == 0
927927
mock_run_command.assert_called_with(['date'], display_cmd=True)
928928

929+
@patch('show.main.run_command')
930+
def test_show_timezone(self, mock_run_command):
931+
runner = CliRunner()
932+
result = runner.invoke(
933+
show.cli.commands['clock'].commands['timezones'], ['--verbose'])
934+
assert result.exit_code == 0
935+
mock_run_command.assert_called_once_with(
936+
['timedatectl', 'list-timezones'], display_cmd=True)
937+
929938
@patch('show.main.run_command')
930939
def test_show_system_memory(self, mock_run_command):
931940
runner = CliRunner()

0 commit comments

Comments
 (0)