diff --git a/kojihub/kojihub.py b/kojihub/kojihub.py index 2581ea6e..69d6eaf5 100644 --- a/kojihub/kojihub.py +++ b/kojihub/kojihub.py @@ -605,7 +605,8 @@ def make_task(method, arglist, **opts): parent: the id of the parent task (creates a subtask) label: (subtasks only) the label of the subtask owner: the user_id that should own the task - channel: the channel to place the task in + channel: requested channel override + default_channel: default channel arch: the arch for the task priority: the priority of the task assign: a host_id to assign the task to @@ -659,6 +660,12 @@ def make_task(method, arglist, **opts): for key in 'arch', 'parent', 'label', 'owner': policy_data[key] = opts[key] policy_data['user_id'] = opts['owner'] + default_channel = 'default' + if 'default_channel' in opts: + default_channel = opts['default_channel'] + if 'channel' not in opts and context.opts.get('DefaultChannelCompat'): + # in the compat case, treat explicit default as an override + opts['channel'] = default_channel if 'channel' in opts: policy_data['req_channel'] = opts['channel'] channel_info = get_channel(opts['channel']) @@ -673,8 +680,8 @@ def make_task(method, arglist, **opts): ruleset = context.policy.get('channel') result = ruleset.apply(policy_data) if result is None: - logger.warning('Channel policy returned no result, using default') - opts['channel_id'] = get_channel_id('default', strict=True) + logger.debug('Channel policy returned no result, using default') + opts['channel_id'] = get_channel_id(default_channel, strict=True) else: try: parts = result.split() @@ -696,6 +703,9 @@ def make_task(method, arglist, **opts): ruleset.last_rule()) raise koji.GenericError("invalid channel policy") opts['channel_id'] = req_channel_id + elif parts[0] == "default": + # note this is different from "use default" if default_channel is passed + opts['channel_id'] = get_channel_id(default_channel, strict=True) else: logger.error("Invalid result from channel policy: %s", ruleset.last_rule()) raise koji.GenericError("invalid channel policy") @@ -10904,7 +10914,7 @@ class RootExports(object): return make_task('chainbuild', [srcs, target, opts], **taskOpts) - def mavenBuild(self, url, target, opts=None, priority=None, channel='maven'): + def mavenBuild(self, url, target, opts=None, priority=None, channel=None): """Create a Maven build task url: The url to checkout the source from. May be a CVS, SVN, or GIT repository. @@ -10912,7 +10922,7 @@ class RootExports(object): priority: the amount to increase (or decrease) the task priority, relative to the default priority; higher values mean lower priority; only admins have the right to specify a negative priority here - channel: the channel to allocate the task to (defaults to the "maven" channel) + channel: override the channel to allocate the task to Returns the task ID """ @@ -10922,7 +10932,7 @@ class RootExports(object): convert_value(url, cast=str, check_only=True) if not opts: opts = {} - taskOpts = {} + taskOpts = {'default_channel': 'maven'} if priority: if priority < 0: if not context.session.hasPerm('admin'): @@ -10933,7 +10943,7 @@ class RootExports(object): return make_task('maven', [url, target, opts], **taskOpts) - def wrapperRPM(self, build, url, target, priority=None, channel='maven', opts=None): + def wrapperRPM(self, build, url, target, priority=None, channel=None, opts=None): """Create a top-level wrapperRPM task build: The build to generate wrapper rpms for. Must be in the COMPLETE state and have no @@ -10945,7 +10955,7 @@ class RootExports(object): priority: the amount to increase (or decrease) the task priority, relative to the default priority; higher values mean lower priority; only admins have the right to specify a negative priority here - channel: the channel to allocate the task to (defaults to the "maven" channel) + channel: override the channel to allocate the task to returns the task ID """ @@ -10968,18 +10978,19 @@ class RootExports(object): logger.warning('The wrapperRPM call ignores repo_id options') del opts['repo_id'] - taskOpts = {} + taskOpts = {'default_channel': 'maven'} if priority: if priority < 0: if not context.session.hasPerm('admin'): raise koji.ActionNotAllowed('only admins may create high-priority tasks') taskOpts['priority'] = koji.PRIO_DEFAULT + priority - convert_value(channel, cast=str, check_only=True) - taskOpts['channel'] = channel + if channel is not None: + convert_value(channel, cast=str, check_only=True) + taskOpts['channel'] = channel return make_task('wrapperRPM', [url, build_target, build, None, opts], **taskOpts) - def chainMaven(self, builds, target, opts=None, priority=None, channel='maven'): + def chainMaven(self, builds, target, opts=None, priority=None, channel=None): """Create a Maven chain-build task builds: a list of maps defining the parameters for the sequence of builds @@ -10987,7 +10998,7 @@ class RootExports(object): priority: the amount to increase (or decrease) the task priority, relative to the default priority; higher values mean lower priority; only admins have the right to specify a negative priority here - channel: the channel to allocate the task to (defaults to the "maven" channel) + channel: override the channel to allocate the task to Returns the task ID """ @@ -10995,7 +11006,7 @@ class RootExports(object): if not context.opts.get('EnableMaven'): raise koji.GenericError("Maven support not enabled") convert_value(builds, cast=dict, check_only=True) - taskOpts = {} + taskOpts = {'default_channel': 'maven'} if priority: if priority < 0: if not context.session.hasPerm('admin'): @@ -11006,7 +11017,7 @@ class RootExports(object): return make_task('chainmaven', [builds, target, opts], **taskOpts) - def winBuild(self, vm, url, target, opts=None, priority=None, channel='vm'): + def winBuild(self, vm, url, target, opts=None, priority=None, channel=None): """ Create a Windows build task @@ -11017,7 +11028,7 @@ class RootExports(object): priority: the amount to increase (or decrease) the task priority, relative to the default priority; higher values mean lower priority; only admins have the right to specify a negative priority here - channel: the channel to allocate the task to (defaults to the "vm" channel) + channel: override the channel to allocate the task to Returns the task ID """ @@ -11032,7 +11043,7 @@ class RootExports(object): assert_policy('vm', policy_data) if not opts: opts = {} - taskOpts = {} + taskOpts = {'default_channel': 'vm'} if priority: if priority < 0: if not context.session.hasPerm('admin'): @@ -11057,7 +11068,7 @@ class RootExports(object): context.session.assertPerm(img_type) - taskOpts = {'channel': img_type} + taskOpts = {'default_channel': img_type} if img_type == 'livemedia': taskOpts['arch'] = 'noarch' else: @@ -11079,7 +11090,7 @@ class RootExports(object): Create an image using two other images and an indirection template """ context.session.assertPerm('image') - taskOpts = {'channel': 'image'} + taskOpts = {'default_channel': 'image'} if priority: if priority < 0: if not context.session.hasPerm('admin'): @@ -11104,7 +11115,7 @@ class RootExports(object): for i in [name, inst_tree, version]: convert_value(i, cast=str, check_only=True) context.session.assertPerm('image') - taskOpts = {'channel': 'image'} + taskOpts = {'default_channel': 'image'} if priority: if priority < 0: if not context.session.hasPerm('admin'): @@ -13742,7 +13753,7 @@ class RootExports(object): logger.debug("Cancelling distRepo task %d" % task_id) Task(task_id).cancel(recurse=True) return make_task('distRepo', [tag, repo_id, keys, task_opts], - priority=15, channel='createrepo') + priority=15, default_channel='createrepo') def newRepo(self, tag, event=None, src=False, debuginfo=False, separate_src=False): """Create a newRepo task. returns task id""" @@ -13770,7 +13781,7 @@ class RootExports(object): if debuginfo: opts['debuginfo'] = True args = koji.encode_args(tag, **opts) - return make_task('newRepo', args, priority=15, channel='createrepo') + return make_task('newRepo', args, priority=15, default_channel='createrepo') def repoExpire(self, repo_id): """mark repo expired""" diff --git a/kojihub/kojixmlrpc.py b/kojihub/kojixmlrpc.py index e0dd0777..f64a340b 100644 --- a/kojihub/kojixmlrpc.py +++ b/kojihub/kojixmlrpc.py @@ -472,6 +472,8 @@ config_map = [ ['EnableMaven', 'boolean', False], ['EnableWin', 'boolean', False], + ['DefaultChannelCompat', 'boolean', True], + ['RLIMIT_AS', 'string', None], ['RLIMIT_CORE', 'string', None], ['RLIMIT_CPU', 'string', None], @@ -630,7 +632,7 @@ _default_policies = { 'channel': ''' has req_channel :: req is_child_task :: parent - all :: use default + all :: default ''', 'vm': ''' has_perm admin win-admin :: allow diff --git a/tests/test_api/data/api.json b/tests/test_api/data/api.json index 95480002..e03f7d25 100644 --- a/tests/test_api/data/api.json +++ b/tests/test_api/data/api.json @@ -5719,7 +5719,7 @@ }, { "name": "channel", - "default": "'maven'" + "default": "None" } ], "varargs": null, @@ -9363,7 +9363,7 @@ }, { "name": "channel", - "default": "'maven'" + "default": "None" } ], "varargs": null, @@ -10312,7 +10312,7 @@ }, { "name": "channel", - "default": "'vm'" + "default": "None" } ], "varargs": null, @@ -10342,7 +10342,7 @@ }, { "name": "channel", - "default": "'maven'" + "default": "None" }, { "name": "opts", diff --git a/tests/test_hub/test_make_task.py b/tests/test_hub/test_make_task.py new file mode 100644 index 00000000..82bbba77 --- /dev/null +++ b/tests/test_hub/test_make_task.py @@ -0,0 +1,133 @@ +import unittest + +from unittest import mock + +from kojihub import kojihub, kojixmlrpc + +QP = kojihub.QueryProcessor +IP = kojihub.InsertProcessor + + +class TestMakeTask(unittest.TestCase): + def getQuery(self, *args, **kwargs): + query = QP(*args, **kwargs) + query.execute = mock.MagicMock() + query.executeOne = mock.MagicMock() + self.queries.append(query) + return query + + def getInsert(self, *args, **kwargs): + insert = IP(*args, **kwargs) + insert.execute = mock.MagicMock() + self.inserts.append(insert) + return insert + + def setUp(self): + self.context = mock.patch('kojihub.kojihub.context').start() + self.context_db = mock.patch('kojihub.db.context').start() + # self.get_user = mock.patch('kojihub.kojihub.get_user').start() + self.opts = { + 'DefaultChannelCompat': True, + 'policy': { + 'channel': kojixmlrpc._default_policies['channel'], + 'priority': kojixmlrpc._default_policies['priority'], + } + } + self.context.opts = self.opts + + self._dml = mock.patch('kojihub.db._dml').start() + self.QueryProcessor = mock.patch('kojihub.kojihub.QueryProcessor', + side_effect=self.getQuery).start() + self.queries = [] + self.InsertProcessor = mock.patch('kojihub.kojihub.InsertProcessor', + side_effect=self.getInsert).start() + self.inserts = [] + + self.get_channel = mock.patch('kojihub.kojihub.get_channel').start() + self.get_channel_id = mock.patch('kojihub.kojihub.get_channel_id').start() + self.currval = mock.patch('kojihub.kojihub.currval').start() + self.auto_arch_refuse = mock.patch('kojihub.scheduler.auto_arch_refuse').start() + + self.set_policy() + + def tearDown(self): + mock.patch.stopall() + + def set_policy(self, opts=None): + # set policy from options + if opts is None: + opts = self.context.opts + plugins = {} + policy = kojixmlrpc.get_policy(opts, plugins) + self.context.policy = policy or {} + + def test_make_task_simple(self): + self.get_channel_id.return_value = 1 + + kojihub.make_task('something', [1, 2, 3]) + + self.get_channel_id.assert_called_once_with('default', strict=True) + self.assertEqual(len(self.inserts), 1) + expected = {'state': 0, 'method': 'something', 'parent': None, 'arch': 'noarch', + 'channel_id': 1} + for key in expected: + self.assertEqual(self.inserts[0].data[key], expected[key]) + + def test_make_task_default_channel_compat(self): + self.get_channel.return_value = {'name': 'testing', 'id': 23, 'enabled': True} + self.opts['DefaultChannelCompat'] = True + self.opts['policy']['channel'] = ''' + has req_channel :: req + all :: use bad + ''' + self.set_policy() + + kojihub.make_task('something', [1, 2, 3], default_channel='testing') + + # in compat mode we expect to hit the "req" policy result + self.get_channel.assert_called_once_with('testing') + self.get_channel_id.assert_not_called() + self.assertEqual(len(self.inserts), 1) + expected = {'state': 0, 'method': 'something', 'parent': None, 'arch': 'noarch', + 'channel_id': 23} + for key in expected: + self.assertEqual(self.inserts[0].data[key], expected[key]) + + def test_make_task_default_channel_nocompat(self): + self.get_channel_id.return_value = 23 + self.opts['DefaultChannelCompat'] = False + self.opts['policy']['channel'] = ''' + has req_channel :: use bad + all :: default + ''' + self.set_policy() + + kojihub.make_task('something', [1, 2, 3], default_channel='testing') + + # without compat mode we expect to hit the "default" policy result + self.get_channel_id.assert_called_once_with('testing', strict=True) + self.get_channel.assert_not_called() + self.assertEqual(len(self.inserts), 1) + expected = {'state': 0, 'method': 'something', 'parent': None, 'arch': 'noarch', + 'channel_id': 23} + for key in expected: + self.assertEqual(self.inserts[0].data[key], expected[key]) + + def test_make_task_no_policy_result(self): + # if channel policy does not match, we should use the default + self.get_channel.return_value = {'name': 'testing', 'id': 23, 'enabled': True} + self.get_channel_id.return_value = 23 + self.opts['policy']['channel'] = '' + self.set_policy() + + kojihub.make_task('something', [1, 2, 3], default_channel='testing') + + self.get_channel_id.assert_called_once_with('testing', strict=True) + self.get_channel.assert_called_once_with('testing') + self.assertEqual(len(self.inserts), 1) + expected = {'state': 0, 'method': 'something', 'parent': None, 'arch': 'noarch', + 'channel_id': 23} + for key in expected: + self.assertEqual(self.inserts[0].data[key], expected[key]) + +# the end