Skip to content

[Clock] Implement clock CLI #2793

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

Merged
merged 3 commits into from
Jun 2, 2023
Merged
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
63 changes: 63 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/sbin/env python

import click
import datetime
import ipaddress
import json
import jsonpatch
Expand Down Expand Up @@ -7103,5 +7104,67 @@ def del_subinterface(ctx, subinterface_name):
except JsonPatchConflict as e:
ctx.fail("{} is invalid vlan subinterface. Error: {}".format(subinterface_name, e))


#
# 'clock' group ('config clock ...')
#
@config.group()
def clock():
"""Configuring system clock"""
pass


def get_tzs(ctx, args, incomplete):
ret = clicommon.run_command('timedatectl list-timezones',
display_cmd=False, ignore_error=False,
return_cmd=True)
if len(ret) == 0:
return []

lst = ret[0].split('\n')
return [k for k in lst if incomplete in k]


@clock.command()
@click.argument('timezone', metavar='<timezone_name>', required=True,
autocompletion=get_tzs)
def timezone(timezone):
"""Set system timezone"""

if timezone not in get_tzs(None, None, ''):
click.echo(f'Timezone {timezone} does not conform format')
sys.exit(1)

config_db = ConfigDBConnector()
config_db.connect()
config_db.mod_entry(swsscommon.CFG_DEVICE_METADATA_TABLE_NAME, 'localhost',
{'timezone': timezone})


@clock.command()
@click.argument('date', metavar='<YYYY-MM-DD>', required=True)
@click.argument('time', metavar='<HH:MM:SS>', required=True)
def date(date, time):
"""Set system date and time"""
valid = True
try:
datetime.datetime.strptime(date, '%Y-%m-%d')
except ValueError:
click.echo(f'Date {date} does not conform format YYYY-MM-DD')
valid = False

try:
datetime.datetime.strptime(time, '%H:%M:%S')
except ValueError:
click.echo(f'Time {time} does not conform format HH:MM:SS')
valid = False

if not valid:
sys.exit(1)

date_time = f'{date} {time}'
clicommon.run_command(['timedatectl', 'set-time', date_time])


if __name__ == '__main__':
config()
56 changes: 56 additions & 0 deletions doc/Command-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,62 @@ This command displays the current date and time configured on the system
Mon Mar 25 20:25:16 UTC 2019
```

**config clock date**

This command will set the date-time of the systetm, given strings with date-time format <YYYY-MM-DD> <HH:MM:SS>

- Usage:
```
config clock date <YYYY-MM-DD> <HH:MM:SS>
```

- Parameters:
- _date_: valid date in format YYYY-MM-DD
- _time_: valid time in format HH:MM:SS

- Example:
```
admin@sonic:~$ config clock date 2023-04-10 13:54:36
```

**config clock timezone**

This command will set the timezone of the systetm, given a string of a valid timezone.

- Usage:
```
config clock timezone <timezone>
```

- Parameters:
- _timezone_: valid timezone to be configured


- Example:
```
admin@sonic:~$ config clock timezone Africa/Accra


**show clock timezones**

This command Will display list of all valid timezones to be configured.

- Usage:
```
show clock timezones
```

- Example:
```
root@host:~$ show clock timezones
Africa/Abidjan
Africa/Accra
Africa/Addis_Ababa
Africa/Algiers
Africa/Asmara
...
```

**show boot**

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
Expand Down
27 changes: 23 additions & 4 deletions show/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1771,13 +1771,32 @@ def uptime(verbose):
cmd = ['uptime', '-p']
run_command(cmd, display_cmd=verbose)

@cli.command()

#
# 'clock' command group ("show clock ...")
#
@cli.group('clock', invoke_without_command=True)
@click.pass_context
@click.option('--verbose', is_flag=True, help="Enable verbose output")
def clock(verbose):
def clock(ctx, verbose):
"""Show date and time"""
cmd = ["date"]
run_command(cmd, display_cmd=verbose)
# If invoking subcomand, no need to do anything
if ctx.invoked_subcommand is not None:
return

run_command(['date'], display_cmd=verbose)


@clock.command()
@click.option('--verbose', is_flag=True, help="Enable verbose output")
def timezones(verbose):
"""List of available timezones"""
run_command(['timedatectl', 'list-timezones'], display_cmd=verbose)


#
# 'system-memory' command ("show system-memory")
#
@cli.command('system-memory')
@click.option('--verbose', is_flag=True, help="Enable verbose output")
def system_memory(verbose):
Expand Down
62 changes: 62 additions & 0 deletions tests/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2363,3 +2363,65 @@ def test_fec(self, mock_run_command):
def teardown(self):
print("TEARDOWN")


class TestConfigClock(object):
timezone_test_val = ['Europe/Kyiv', 'Asia/Israel', 'UTC']

@classmethod
def setup_class(cls):
print('SETUP')
import config.main
importlib.reload(config.main)

@patch('config.main.get_tzs', mock.Mock(return_value=timezone_test_val))
def test_timezone_good(self):
runner = CliRunner()
obj = {'db': Db().cfgdb}

result = runner.invoke(
config.config.commands['clock'].commands['timezone'],
['UTC'], obj=obj)

assert result.exit_code == 0

@patch('config.main.get_tzs', mock.Mock(return_value=timezone_test_val))
def test_timezone_bad(self):
runner = CliRunner()
obj = {'db': Db().cfgdb}

result = runner.invoke(
config.config.commands['clock'].commands['timezone'],
['Atlantis'], obj=obj)

assert result.exit_code != 0
assert 'Timezone Atlantis does not conform format' in result.output

@patch('utilities_common.cli.run_command',
mock.MagicMock(side_effect=mock_run_command_side_effect))
def test_date_good(self):
runner = CliRunner()
obj = {'db': Db().cfgdb}

result = runner.invoke(
config.config.commands['clock'].commands['date'],
['2020-10-10', '10:20:30'], obj=obj)

assert result.exit_code == 0

@patch('utilities_common.cli.run_command',
mock.MagicMock(side_effect=mock_run_command_side_effect))
def test_date_bad(self):
runner = CliRunner()
obj = {'db': Db().cfgdb}

result = runner.invoke(
config.config.commands['clock'].commands['date'],
['20-10-10', '60:70:80'], obj=obj)

assert result.exit_code != 0
assert 'Date 20-10-10 does not conform format' in result.output
assert 'Time 60:70:80 does not conform format' in result.output

@classmethod
def teardown_class(cls):
print('TEARDOWN')
9 changes: 9 additions & 0 deletions tests/show_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,15 @@ def test_show_clock(self, mock_run_command):
assert result.exit_code == 0
mock_run_command.assert_called_with(['date'], display_cmd=True)

@patch('show.main.run_command')
def test_show_timezone(self, mock_run_command):
runner = CliRunner()
result = runner.invoke(
show.cli.commands['clock'].commands['timezones'], ['--verbose'])
assert result.exit_code == 0
mock_run_command.assert_called_once_with(
['timedatectl', 'list-timezones'], display_cmd=True)

@patch('show.main.run_command')
def test_show_system_memory(self, mock_run_command):
runner = CliRunner()
Expand Down