From 421555cd41fea6733ee0125e5e5268b3efc086a5 Mon Sep 17 00:00:00 2001 From: Jana Cupova Date: Mar 24 2021 13:00:06 +0000 Subject: api getLastHostUpdate returns timestamp fixes: https://pagure.io/koji/issue/2497 --- diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index 9b4e151..9f44637 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -2888,14 +2888,14 @@ def anon_handle_list_hosts(goptions, session, args): # pull in the last update using multicall to speed it up a bit session.multicall = True for host in hosts: - session.getLastHostUpdate(host['id']) + session.getLastHostUpdate(host['id'], ts=True) updateList = session.multiCall() for host, [update] in zip(hosts, updateList): if update is None: host['update'] = '-' else: - host['update'] = update.split('.')[0] + host['update'] = koji.formatTimeLong(update) host['enabled'] = yesno(host['enabled']) host['ready'] = yesno(host['ready']) host['arches'] = ','.join(host['arches'].split()) @@ -3355,11 +3355,11 @@ def anon_handle_hostinfo(goptions, session, args): print("Comment:") print("Enabled: %s" % (info['enabled'] and 'yes' or 'no')) print("Ready: %s" % (info['ready'] and 'yes' or 'no')) - update = session.getLastHostUpdate(info['id']) + update = session.getLastHostUpdate(info['id'], ts=True) if update is None: update = "never" else: - update = update[:update.find('.')] + update = koji.formatTimeLong(update) print("Last Update: %s" % update) print("Channels: %s" % ' '.join([c['name'] for c in session.listChannels(hostID=info['id'])])) diff --git a/hub/kojihub.py b/hub/kojihub.py index e175415..a979a84 100644 --- a/hub/kojihub.py +++ b/hub/kojihub.py @@ -12636,8 +12636,8 @@ class RootExports(object): tables=tables, joins=joins, clauses=clauses, values=locals()) return query.execute() - def getLastHostUpdate(self, hostID): - """Return the latest update timestampt for the host + def getLastHostUpdate(self, hostID, ts=False): + """Return the latest update timestamp for the host The timestamp represents the last time the host with the given ID contacted the hub. Returns None if the host has never contacted @@ -12648,7 +12648,11 @@ class RootExports(object): ORDER BY update_time DESC LIMIT 1 """ - return _singleValue(query, locals(), strict=False) + date = _singleValue(query, locals(), strict=False) + if ts and date is not None: + return date.timestamp() + else: + return date getAllArches = staticmethod(get_all_arches) diff --git a/tests/test_cli/test_hostinfo.py b/tests/test_cli/test_hostinfo.py index 8a50872..6132e54 100644 --- a/tests/test_cli/test_hostinfo.py +++ b/tests/test_cli/test_hostinfo.py @@ -1,5 +1,8 @@ from __future__ import absolute_import import mock +import os +import time +import locale from six.moves import StringIO import koji @@ -9,10 +12,15 @@ from . import utils class TestHostinfo(utils.CliTestCase): def setUp(self): + # force locale to compare 'expect' value + locale.setlocale(locale.LC_ALL, ('en_US', 'UTF-8')) self.options = mock.MagicMock() self.options.debug = False self.session = mock.MagicMock() self.session.getAPIVersion.return_value = koji.API_VERSION + self.original_timezone = os.environ.get('TZ') + os.environ['TZ'] = 'UTC' + time.tzset() self.hostinfo = {'arches': 'x86_64', 'capacity': 2.0, 'comment': None, @@ -23,9 +31,55 @@ class TestHostinfo(utils.CliTestCase): 'ready': True, 'task_load': 0.0, 'user_id': 2} - self.last_update = '2021-03-16 06:19:14.862938+00:00' + self.last_update = 1615875554.862938 self.list_channels = [{'id': 1, 'name': 'default'}, {'id': 2, 'name': 'createrepo'}] + def tearDown(self): + locale.resetlocale() + if self.original_timezone is None: + del os.environ['TZ'] + else: + os.environ['TZ'] = self.original_timezone + time.tzset() + + @mock.patch('sys.stderr', new_callable=StringIO) + def test_hostinfo_without_option(self, stderr): + expected = "Usage: %s hostinfo [options] [ ...]\n" \ + "(Specify the --help global option for a list of other help options)\n\n" \ + "%s: error: Please specify a host\n" % (self.progname, self.progname) + self.session.getChannel.return_value = None + with self.assertRaises(SystemExit) as ex: + anon_handle_hostinfo(self.options, self.session, []) + self.assertExitCode(ex, 2) + self.assert_console_message(stderr, expected) + + @mock.patch('sys.stdout', new_callable=StringIO) + def test_hostinfo_valid(self, stdout): + expected = """Name: kojibuilder +ID: 1 +Arches: x86_64 +Capacity: 2.0 +Task Load: 0.00 +Description: +Comment: +Enabled: yes +Ready: yes +Last Update: Tue, 16 Mar 2021 06:19:14 UTC +Channels: default createrepo +Active Buildroots: +None +""" + self.session.getHost.return_value = self.hostinfo + self.session.getLastHostUpdate.return_value = self.last_update + self.session.listChannels.return_value = self.list_channels + rv = anon_handle_hostinfo(self.options, self.session, [self.hostinfo['name']]) + self.assertEqual(rv, None) + self.assert_console_message(stdout, expected) + self.session.getHost.assert_called_once_with(self.hostinfo['name']) + self.session.getLastHostUpdate.assert_called_once_with(self.hostinfo['id'], ts=True) + self.session.listChannels.assert_called_once_with(hostID=self.hostinfo['id']) + self.assertEqual(self.session.listBuildroots.call_count, 3) + @mock.patch('sys.stderr', new_callable=StringIO) @mock.patch('sys.stdout', new_callable=StringIO) def test_hostinfo_more_hosts_with_non_exit_host(self, stdout, stderr): @@ -40,7 +94,7 @@ Description: Comment: Enabled: yes Ready: yes -Last Update: 2021-03-16 06:19:14 +Last Update: Tue, 16 Mar 2021 06:19:14 UTC Channels: default createrepo Active Buildroots: None @@ -56,7 +110,7 @@ None self.assert_console_message(stdout, expected_stdout) self.assert_console_message(stderr, expected_error) self.assertEqual(self.session.getHost.call_count, 2) - self.session.getLastHostUpdate.assert_called_once_with(self.hostinfo['id']) + self.session.getLastHostUpdate.assert_called_once_with(self.hostinfo['id'], ts=True) self.session.listChannels.assert_called_once_with(hostID=self.hostinfo['id']) self.assertEqual(self.session.listBuildroots.call_count, 3) diff --git a/tests/test_cli/test_list_hosts.py b/tests/test_cli/test_list_hosts.py index 54a7175..40fa5ee 100644 --- a/tests/test_cli/test_list_hosts.py +++ b/tests/test_cli/test_list_hosts.py @@ -1,6 +1,8 @@ from __future__ import absolute_import - import mock +import os +import time +import locale from six.moves import StringIO import koji @@ -10,13 +12,51 @@ from . import utils class TestListHosts(utils.CliTestCase): def setUp(self): + # force locale to compare 'expect' value + locale.setlocale(locale.LC_ALL, ('en_US', 'UTF-8')) self.options = mock.MagicMock() self.options.debug = False self.session = mock.MagicMock() self.session.getAPIVersion.return_value = koji.API_VERSION + self.original_timezone = os.environ.get('TZ') + os.environ['TZ'] = 'UTC' + time.tzset() + + def tearDown(self): + locale.resetlocale() + if self.original_timezone is None: + del os.environ['TZ'] + else: + os.environ['TZ'] = self.original_timezone + time.tzset() + + @mock.patch('sys.stdout', new_callable=StringIO) + @mock.patch('koji_cli.commands.ensure_connection') + def test_list_hosts_valid(self, ensure_connection, stdout): + host_update = 1615875554.862938 + expected = """kojibuilder Y Y 0.0/2.0 x86_64 Tue, 16 Mar 2021 06:19:14 UTC +""" + list_hosts = [{'arches': 'x86_64', + 'capacity': 2.0, + 'comment': None, + 'description': None, + 'enabled': True, + 'id': 1, + 'name': 'kojibuilder', + 'ready': True, + 'task_load': 0.0, + 'user_id': 2}] + self.session.getLastHostUpdate.return_value = host_update + self.session.multiCall.return_value = [[host_update]] + self.session.listHosts.return_value = list_hosts + rv = anon_handle_list_hosts(self.options, self.session, []) + self.assertEqual(rv, None) + self.assert_console_message(stdout, expected) + self.session.listHosts.assert_called_once_with() + self.session.getLastHostUpdate.assert_called_once_with(list_hosts[0]['id'], ts=True) @mock.patch('sys.stderr', new_callable=StringIO) - def test_list_pkgs_non_exist_channel(self, stderr): + def test_list_hosts_non_exist_channel(self, stderr): channel = 'test-channel' expected = "Usage: %s list-hosts [options]\n" \ "(Specify the --help global option for a list of other help options)\n\n" \ diff --git a/tests/test_hub/test_get_last_host_update.py b/tests/test_hub/test_get_last_host_update.py new file mode 100644 index 0000000..ee2a6b6 --- /dev/null +++ b/tests/test_hub/test_get_last_host_update.py @@ -0,0 +1,38 @@ +import unittest +import mock +import datetime +import sys + +import kojihub + + +class TestGetLastHostUpdate(unittest.TestCase): + + def setUp(self): + self.exports = kojihub.RootExports() + + @mock.patch('kojihub._singleValue') + def test_valid_ts(self, _singleValue): + expected = 1615875554.862938 + if sys.version_info[1] <= 6: + dt = datetime.datetime.strptime( + "2021-03-16T06:19:14.862938+0000", "%Y-%m-%dT%H:%M:%S.%f%z") + else: + dt = datetime.datetime.strptime( + "2021-03-16T06:19:14.862938+00:00", "%Y-%m-%dT%H:%M:%S.%f%z") + _singleValue.return_value = dt + rv = self.exports.getLastHostUpdate(1, ts=True) + self.assertEqual(rv, expected) + + @mock.patch('kojihub._singleValue') + def test_valid_datetime(self, _singleValue): + if sys.version_info[1] <= 6: + dt = datetime.datetime.strptime( + "2021-03-16T06:19:14.862938+0000", "%Y-%m-%dT%H:%M:%S.%f%z") + else: + dt = datetime.datetime.strptime( + "2021-03-16T06:19:14.862938+00:00", "%Y-%m-%dT%H:%M:%S.%f%z") + expected = "2021-03-16T06:19:14.862938+00:00" + _singleValue.return_value = dt + rv = self.exports.getLastHostUpdate(1) + self.assertEqual(rv, dt) diff --git a/www/kojiweb/index.py b/www/kojiweb/index.py index 75f6651..ebc61b7 100644 --- a/www/kojiweb/index.py +++ b/www/kojiweb/index.py @@ -1637,7 +1637,7 @@ def hosts(environ, state='enabled', start=None, order='name'): server.multicall = True for host in hosts: - server.getLastHostUpdate(host['id']) + server.getLastHostUpdate(host['id'], ts=True) updates = server.multiCall() for host, [lastUpdate] in zip(hosts, updates): host['last_update'] = lastUpdate @@ -1680,7 +1680,7 @@ def hostinfo(environ, hostID=None, userID=None): values['host'] = host values['channels'] = channels values['buildroots'] = buildroots - values['lastUpdate'] = server.getLastHostUpdate(host['id']) + values['lastUpdate'] = server.getLastHostUpdate(host['id'], ts=True) if environ['koji.currentUser']: values['perms'] = server.getUserPerms(environ['koji.currentUser']['id']) else: