From 437afbb720c75ac1dc6b63174ff650dcf190445c Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Wed, 26 Oct 2016 12:52:19 +0200 Subject: [PATCH] CLI for save_failed_tree --- cli/koji | 46 +++++++++ plugins/builder/save_failed_tree.py | 3 +- plugins/hub/save_failed_tree.py | 7 +- tests/test_cli/data/list-commands.txt | 1 + tests/test_cli/test_save_failed_tree.py | 128 ++++++++++++++++++++++++ 5 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 tests/test_cli/test_save_failed_tree.py diff --git a/cli/koji b/cli/koji index 6eb48f3e..ca9a8a63 100755 --- a/cli/koji +++ b/cli/koji @@ -7202,6 +7202,52 @@ def handle_runroot(options, session, args): sys.exit(1) return +def handle_save_failed_tree(options, session, args): + "Create tarball with whole buildtree" + usage = _("usage: %prog save-failed-tree [options] taskID") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = OptionParser(usage=usage) + parser.disable_interspersed_args() + parser.add_option("-f", "--full", action="store_true", default=False, + help=_("Download whole tree, if not specified, only builddir will be downloaded")) + parser.add_option("--quiet", action="store_true", + help=_("Do not print the task information"), default=options.quiet) + parser.add_option("--nowait", action="store_true", + help=_("Don't wait on build")) + + (opts, args) = parser.parse_args(args) + + if len(args) != 1: + parser.error(_("List exactly one taskID")) + + try: + taskID = int(args[0]) + except ValueError: + parser.error(_("Task ID must be an integer.")) + + activate_session(session) + try: + task_id = session.saveFailedTree(taskID, opts.full) + except koji.GenericError as e: + if 'Invalid method' in str(e): + print "* The save_failed_tree plugin appears to not be installed" \ + " on the koji hub. Please contact the administrator." + raise + + if type(task_id) != int: + print 'Error: %s' % task_id + return + + if not opts.quiet: + print "Created task:", task_id + print "Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id) + + if opts.nowait: + return + else: + session.logout() + watch_tasks(session, [task_id], quiet=opts.quiet) + def handle_help(options, session, args): "[info] List available commands" diff --git a/plugins/builder/save_failed_tree.py b/plugins/builder/save_failed_tree.py index 784bf205..223e821c 100644 --- a/plugins/builder/save_failed_tree.py +++ b/plugins/builder/save_failed_tree.py @@ -6,6 +6,7 @@ from __main__ import BuildRoot __all__ = ('SaveFailedTreeTask',) + def omit_ccache(tarinfo): if fnmatch.fnmatch(tarinfo.name, '*/tmp/krb5cc') or \ fnmatch.fnmatch(tarinfo.name, '*/etc/*.keytab'): @@ -14,12 +15,10 @@ def omit_ccache(tarinfo): return tarinfo - class SaveFailedTreeTask(tasks.BaseTaskHandler): Methods = ['saveFailedTree'] _taskWeight = 3.0 - def handler(self, taskID, full=False): self.logger.debug("Starting saving buildroots for task %d [full=%s]" % (taskID, full)) tar_path = os.path.join(self.workdir, 'broots-task-%s.tar.gz' % taskID) diff --git a/plugins/hub/save_failed_tree.py b/plugins/hub/save_failed_tree.py index 9eccca64..7f986024 100644 --- a/plugins/hub/save_failed_tree.py +++ b/plugins/hub/save_failed_tree.py @@ -1,14 +1,19 @@ +import sys import koji from koji.plugin import export -import sys sys.path.insert(0, '/usr/share/koji-hub/') import kojihub __all__ = ('saveFailedTree',) + @export def saveFailedTree(taskID, full=False, **opts): + '''xmlrpc method for creating saveFailedTree task. If arguments are + invalid, error message is returned. Otherwise task id of newly created + task is returned.''' + # let it raise errors taskID = int(taskID) full = bool(full) diff --git a/tests/test_cli/data/list-commands.txt b/tests/test_cli/data/list-commands.txt index 7a4d8ce9..6249a735 100644 --- a/tests/test_cli/data/list-commands.txt +++ b/tests/test_cli/data/list-commands.txt @@ -118,6 +118,7 @@ miscellaneous commands: call Execute an arbitrary XML-RPC call import-comps Import group/package information from a comps file moshimoshi Introduce yourself + save-failed-tree Create tarball with whole buildtree monitor commands: wait-repo Wait for a repo to be regenerated diff --git a/tests/test_cli/test_save_failed_tree.py b/tests/test_cli/test_save_failed_tree.py new file mode 100644 index 00000000..90488fe3 --- /dev/null +++ b/tests/test_cli/test_save_failed_tree.py @@ -0,0 +1,128 @@ +import StringIO +import unittest +import koji +import mock + +import loadcli +cli = loadcli.cli + + +class TestSaveFailedTree(unittest.TestCase): + def setUp(self): + self.options = mock.MagicMock() + self.session = mock.MagicMock() + self.args = mock.MagicMock() + self.original_parser = cli.OptionParser + cli.OptionParser = mock.MagicMock() + self.parser = cli.OptionParser.return_value + cli.options = self.options # globals!!! + + def tearDown(self): + cli.OptionParser = self.original_parser + + # Show long diffs in error output... + maxDiff = None + + @mock.patch('koji_cli.activate_session') + def test_handle_save_failed_tree_simple(self, activate_session_mock): + # koji save-failed-tree 123456 + task_id = 123456 + arguments = [task_id] + options = mock.MagicMock() + options.full = False + options.nowait = True + self.parser.parse_args.return_value = [options, arguments] + self.session.getAPIVersion.return_value = koji.API_VERSION + + # Mock out the xmlrpc server + self.session.saveFailedTree.return_value = 123 + + # Run it and check immediate output + cli.handle_save_failed_tree(self.options, self.session, self.args) + + # Finally, assert that things were called as we expected. + activate_session_mock.assert_called_once_with(self.session) + self.session.saveFailedTree.assert_called_once_with(task_id, options.full) + + @mock.patch('koji_cli.activate_session') + def test_handle_save_failed_tree_full(self, activate_session_mock): + # koji save-failed-tree 123456 --full + task_id = 123456 + arguments = [task_id] + options = mock.MagicMock() + options.full = True + options.nowait = True + self.parser.parse_args.return_value = [options, arguments] + self.session.getAPIVersion.return_value = koji.API_VERSION + + # Mock out the xmlrpc server + self.session.saveFailedTree.return_value = 123 + + # Run it and check immediate output + cli.handle_save_failed_tree(self.options, self.session, self.args) + + # Finally, assert that things were called as we expected. + activate_session_mock.assert_called_once_with(self.session) + self.session.saveFailedTree.assert_called_once_with(task_id, options.full) + + @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.watch_tasks') + def test_handle_save_failed_tree_wait(self, watch_tasks_mock, activate_session_mock): + # koji save-failed-tree 123456 --full + task_id = 123456 + arguments = [task_id] + options = mock.MagicMock() + options.full = True + options.nowait = False + options.quiet = False + self.parser.parse_args.return_value = [options, arguments] + self.session.getAPIVersion.return_value = koji.API_VERSION + + # Mock out the xmlrpc server + spawned_id = 123 + self.session.saveFailedTree.return_value = spawned_id + + # Run it and check immediate output + cli.handle_save_failed_tree(self.options, self.session, self.args) + + # Finally, assert that things were called as we expected. + self.session.saveFailedTree.assert_called_once_with(task_id, options.full) + activate_session_mock.assert_called_once_with(self.session) + self.session.logout.assert_called_once_with() + watch_tasks_mock.assert_called_once_with(self.session, [spawned_id], + quiet=options.quiet) + + @mock.patch('sys.stdout', new_callable=StringIO.StringIO) + @mock.patch('koji_cli.activate_session') + @mock.patch('koji_cli.watch_tasks') + def test_handle_save_failed_tree_errors(self, watch_tasks_mock, activate_session_mock, stdout): + # koji save-failed-tree 123 456 + arguments = [123, 456] + options = mock.MagicMock() + self.parser.parse_args.return_value = [options, arguments] + self.parser.error.side_effect = Exception() + self.session.getAPIVersion.return_value = koji.API_VERSION + + self.assertRaises(Exception, cli.handle_save_failed_tree, + self.options, self.session, self.args) + + arguments = ["text"] + self.parser.parse_args.return_value = [options, arguments] + self.assertRaises(Exception, cli.handle_save_failed_tree, self.options, + self.session, self.args) + + # plugin not installed + arguments = [123] + self.parser.parse_args.return_value = [options, arguments] + self.session.saveFailedTree.side_effect = koji.GenericError("Invalid method") + self.assertRaises(koji.GenericError, cli.handle_save_failed_tree, + self.options, self.session, self.args) + + # something wrong happened in task + stdout.seek(0) + stdout.truncate() + self.session.saveFailedTree.return_value = 'xyz' + self.session.saveFailedTree.side_effect = None + cli.handle_save_failed_tree(self.options, self.session, self.args) + actual = stdout.getvalue() + self.assertEqual(actual, 'Error: xyz\n')