fix(scheduler): resolve limit host and project scopes against their own groups (bug-1933)
The limit plugin called build_limit() with the host-group and project-group managers swapped, so a limit's hosts: members were looked up among the project groups (a host-group name became a literal host name and the limit never applied) and its projects: members among the host groups. Pass them in the order build_limit() declares. New test test_limit_scopes_resolve_against_own_group_kind; the test's main() now initializes the interned-string table that group names need. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
25cdbc79b4
commit
6ecd418d21
4 changed files with 229 additions and 2 deletions
|
|
@ -17722,6 +17722,32 @@
|
|||
"record": ".wolf/bugs/bug-1932.md",
|
||||
"gitea_issue": 357,
|
||||
"gitea_synced_status": "fixed"
|
||||
},
|
||||
{
|
||||
"id": "bug-1933",
|
||||
"status": "open",
|
||||
"status_updated": "2026-10-10",
|
||||
"status_note": "newly logged",
|
||||
"component": "scheduler",
|
||||
"severity": "wrong-result",
|
||||
"timestamp": "2026-10-10T03:36:53Z",
|
||||
"last_seen": "2026-10-10T03:36:53Z",
|
||||
"occurrences": 1,
|
||||
"error_message": "xce_scheduler limit plugin resolves hosts: scopes against project groups (host-group limits ignored)\n\n**Observed:**\nThe scheduler's limit plugin built every limit with the host-group and\nproject-group managers swapped. A limit scoped to a host group never\nmatched that group's hosts; a limit scoped to projects resolved its member\nnames against the host groups. The new regression test, before the fix:\n```\n../scheduler/testsuite/test_limit_plugin.c:491:test_limit_scopes_resolve_against_own_group_kind:FAIL: Expected 1 Was 0\n```\n(host group `hg1` = {`hostA`}; a limit with `hosts: [hg1]` did not match\nhost `hostA`.)\n\n**Expected:** a limit's `hosts:` members resolve against the host groups\n(`hosts: [hg1]` matches every host in `hg1`), and its `projects:` members\nresolve against the project groups (a project name never matches a host\ngroup).\n**Impact:** wrong result, silent. Every host limit that names a host group\napplies to no host (only to a host literally named like the group), so the\nlimit is never enforced. A project limit whose member name equals a host\ngroup's name matches the projects named like that group's hosts instead of\nthe project itself. Limits naming plain host or project names, and limits\nwithout host/project scopes, are unaffected. In addition, the groups a\nlimit builds for its own scopes were registered in the wrong manager\n(`<limit>-pg-group` in the host groups, `<limit>-hg-group` in the project\ngroups).\n**Found by:** review, during `/kiro-spec-requirements project-config`\n(2026-10-10, codebase survey of project-scoped limits).",
|
||||
"file": "src/scheduler/src/plugins/limit.c, src/lib/xcelimit.c",
|
||||
"root_cause": "`build_limit()` (`src/lib/xcelimit.c:292`) takes its group managers as\n`(ugmgr, qgmgr, hgmgr, pgmgr)`, but `limit_seed_config_impl()`\n(`src/scheduler/src/plugins/limit.c:77`) passed\n`(core->ugmgr, core->qgmgr, core->pgmgr, core->hgmgr)`. `build_consumer()`\ntherefore built the HOST consumer with the project-group manager and the\nPROJECT consumer with the host-group manager. `make_g_member()`\n(`src/lib/group.c`) treats a member name as a nested group only if the\nmanager it is given knows that group, so `hg1` became a literal host name\n(the project-group manager is empty) and a project member named like a host\ngroup became that host group. Since `ecf1916d` (2026-01-12, \"support\nlimit\"), the first version of the plugin call; carried through `7c229284`\nand `487e7b94`. Scope: `limit.c:77` is the only production caller of\n`build_limit()` (checked with `grep -rn \"build_limit(\"`); the lib tests\n(`src/lib/testsuite/test_limit.c`) pass the managers in the right order.",
|
||||
"fix": "`src/scheduler/src/plugins/limit.c`: pass `(core->hgmgr), (core->pgmgr)` in\nthe order `build_limit()` declares, so each scope resolves against its own\nkind of group. No wire, WAL, snapshot or config change. The project-group\nmanager is still never loaded from config (projects have no config yet);\nthat is `project-config` scope (Requirement 6.7).\n`src/scheduler/testsuite/test_limit_plugin.c`: new regression test; `main()`\nnow initializes the interned-string table (`xstring_table_init()`), which\ngroup names need and which no earlier test in this binary exercised.\n\nVerification:\n- Test: `test_limit_scopes_resolve_against_own_group_kind`\n (`src/scheduler/testsuite/test_limit_plugin.c`): RED before the fix\n (`Expected 1 Was 0` at line 491, `hosts: [hg1]` not matching `hostA`),\n GREEN after (6/6 in `sched_limit_plugin_test`).\n- Suites: scheduler suite 119/119 OK, libxce tests 2/2 OK, asan\n (`xce.build`), 2026-10-10.\n- Live: n/a: the change is one argument order in config-time limit\n construction, fully covered by the unit test; no live limit run.",
|
||||
"tags": [
|
||||
"scheduler",
|
||||
"limit",
|
||||
"project",
|
||||
"hostgroup"
|
||||
],
|
||||
"related_bugs": [
|
||||
"bug-1785"
|
||||
],
|
||||
"fix_commits": [],
|
||||
"record": ".wolf/bugs/bug-1933.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
104
.wolf/bugs/bug-1933.md
Normal file
104
.wolf/bugs/bug-1933.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
---
|
||||
id: "bug-1933"
|
||||
status: "open"
|
||||
status_updated: "2026-10-10"
|
||||
status_note: "newly logged"
|
||||
component: "scheduler"
|
||||
severity: "wrong-result"
|
||||
timestamp: "2026-10-10T03:36:53Z"
|
||||
last_seen: "2026-10-10T03:36:53Z"
|
||||
occurrences: 1
|
||||
files: ["src/scheduler/src/plugins/limit.c", "src/lib/xcelimit.c"]
|
||||
tags: ["scheduler", "limit", "project", "hostgroup"]
|
||||
related: ["bug-1785"]
|
||||
---
|
||||
|
||||
# bug-1933: xce_scheduler limit plugin resolves hosts: scopes against project groups (host-group limits ignored)
|
||||
|
||||
## Description
|
||||
**Observed:**
|
||||
The scheduler's limit plugin built every limit with the host-group and
|
||||
project-group managers swapped. A limit scoped to a host group never
|
||||
matched that group's hosts; a limit scoped to projects resolved its member
|
||||
names against the host groups. The new regression test, before the fix:
|
||||
```
|
||||
../scheduler/testsuite/test_limit_plugin.c:491:test_limit_scopes_resolve_against_own_group_kind:FAIL: Expected 1 Was 0
|
||||
```
|
||||
(host group `hg1` = {`hostA`}; a limit with `hosts: [hg1]` did not match
|
||||
host `hostA`.)
|
||||
|
||||
**Expected:** a limit's `hosts:` members resolve against the host groups
|
||||
(`hosts: [hg1]` matches every host in `hg1`), and its `projects:` members
|
||||
resolve against the project groups (a project name never matches a host
|
||||
group).
|
||||
**Impact:** wrong result, silent. Every host limit that names a host group
|
||||
applies to no host (only to a host literally named like the group), so the
|
||||
limit is never enforced. A project limit whose member name equals a host
|
||||
group's name matches the projects named like that group's hosts instead of
|
||||
the project itself. Limits naming plain host or project names, and limits
|
||||
without host/project scopes, are unaffected. In addition, the groups a
|
||||
limit builds for its own scopes were registered in the wrong manager
|
||||
(`<limit>-pg-group` in the host groups, `<limit>-hg-group` in the project
|
||||
groups).
|
||||
**Found by:** review, during `/kiro-spec-requirements project-config`
|
||||
(2026-10-10, codebase survey of project-scoped limits).
|
||||
|
||||
## Environment
|
||||
- OS: no /etc/os-release; Docker container; NixOS kernel; kernel Linux 6.18.33 x86_64
|
||||
- libc: glibc 2.42-47 (interpreter /nix/store/wb6rhpznjfczwlwx23zmdrrw74bayxw4-glibc-2.42-47/lib/ld-linux-x86-64.so.2)
|
||||
- Toolchain: gcc (GCC) 15.2.0, meson 1.9.1
|
||||
- Build: asan (xce.build), `ASAN_OPTIONS=detect_leaks=0`
|
||||
- Code: scheduler-framework @ 25cdbc79-dirty
|
||||
- Deployment: n/a (unit test of the scheduler limit plugin); the defect is in
|
||||
code shared by the live scheduler and `xce_scheduler --sim`.
|
||||
|
||||
## Reproduction
|
||||
1. `cd src && nix develop . --command meson compile -C xce.build sched_limit_plugin_test`
|
||||
2. `ASAN_OPTIONS=detect_leaks=0 ./xce.build/scheduler/testsuite/sched_limit_plugin_test`
|
||||
3. Test `test_limit_scopes_resolve_against_own_group_kind`
|
||||
(`src/scheduler/testsuite/test_limit_plugin.c`): loads host group
|
||||
`hg1` = {`hostA`}, configures limit `host_scoped` with `hosts: [hg1]` and
|
||||
limit `project_scoped` with `projects: [hg1]`, then checks
|
||||
`match_limit()` for host `hostA`/`hostB` and project `hostA`/`hg1`.
|
||||
- Frequency: deterministic
|
||||
- Evidence: the test above; no logs kept.
|
||||
|
||||
## Root cause
|
||||
`build_limit()` (`src/lib/xcelimit.c:292`) takes its group managers as
|
||||
`(ugmgr, qgmgr, hgmgr, pgmgr)`, but `limit_seed_config_impl()`
|
||||
(`src/scheduler/src/plugins/limit.c:77`) passed
|
||||
`(core->ugmgr, core->qgmgr, core->pgmgr, core->hgmgr)`. `build_consumer()`
|
||||
therefore built the HOST consumer with the project-group manager and the
|
||||
PROJECT consumer with the host-group manager. `make_g_member()`
|
||||
(`src/lib/group.c`) treats a member name as a nested group only if the
|
||||
manager it is given knows that group, so `hg1` became a literal host name
|
||||
(the project-group manager is empty) and a project member named like a host
|
||||
group became that host group. Since `ecf1916d` (2026-01-12, "support
|
||||
limit"), the first version of the plugin call; carried through `7c229284`
|
||||
and `487e7b94`. Scope: `limit.c:77` is the only production caller of
|
||||
`build_limit()` (checked with `grep -rn "build_limit("`); the lib tests
|
||||
(`src/lib/testsuite/test_limit.c`) pass the managers in the right order.
|
||||
|
||||
## Fix
|
||||
`src/scheduler/src/plugins/limit.c`: pass `(core->hgmgr), (core->pgmgr)` in
|
||||
the order `build_limit()` declares, so each scope resolves against its own
|
||||
kind of group. No wire, WAL, snapshot or config change. The project-group
|
||||
manager is still never loaded from config (projects have no config yet);
|
||||
that is `project-config` scope (Requirement 6.7).
|
||||
`src/scheduler/testsuite/test_limit_plugin.c`: new regression test; `main()`
|
||||
now initializes the interned-string table (`xstring_table_init()`), which
|
||||
group names need and which no earlier test in this binary exercised.
|
||||
|
||||
## Verification
|
||||
- Test: `test_limit_scopes_resolve_against_own_group_kind`
|
||||
(`src/scheduler/testsuite/test_limit_plugin.c`): RED before the fix
|
||||
(`Expected 1 Was 0` at line 491, `hosts: [hg1]` not matching `hostA`),
|
||||
GREEN after (6/6 in `sched_limit_plugin_test`).
|
||||
- Suites: scheduler suite 119/119 OK, libxce tests 2/2 OK, asan
|
||||
(`xce.build`), 2026-10-10.
|
||||
- Live: n/a: the change is one argument order in config-time limit
|
||||
construction, fully covered by the unit test; no live limit run.
|
||||
|
||||
## References
|
||||
- Found during: `.kiro/specs/project-config/requirements.md` (Requirement 6.7)
|
||||
- Introduced: `ecf1916d`
|
||||
|
|
@ -74,8 +74,10 @@ static int limit_seed_config_impl(xce_sched_plugin_t* plugin,
|
|||
int rc;
|
||||
xce_limit_t* limit;
|
||||
|
||||
/* bug-1933: build_limit() takes the host groups before the
|
||||
* project groups */
|
||||
rc = build_limit(&limit, (core->ugmgr), (core->qgmgr),
|
||||
(core->pgmgr), (core->hgmgr), limits[i]);
|
||||
(core->hgmgr), (core->pgmgr), limits[i]);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@
|
|||
#include "scheduler.h"
|
||||
#include "xce/types.h"
|
||||
#include "xcelimit.h"
|
||||
#include "xstring.h"
|
||||
|
||||
void reset_sched_obj(xce_sched_obj_t* obj)
|
||||
{
|
||||
|
|
@ -420,13 +421,107 @@ static void test_get_configured_limits_registered_with_zero_limits(void)
|
|||
iter_destroy(&it);
|
||||
}
|
||||
|
||||
/* bug-1933: build_limit() takes (ugmgr, qgmgr, hgmgr, pgmgr) but the
|
||||
* plugin passed (ugmgr, qgmgr, pgmgr, hgmgr), so a limit's hosts: scope
|
||||
* resolved its members against the project groups (a host-group name
|
||||
* became a literal host name) and its projects: scope against the host
|
||||
* groups (a project named like a host group matched that group's hosts).
|
||||
* With host group "hg1" = {hostA}: a hosts: [hg1] limit must match hostA,
|
||||
* and a projects: [hg1] limit must not match project "hostA". */
|
||||
static void test_limit_scopes_resolve_against_own_group_kind(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
xce_sched_plugin_t plugin;
|
||||
xce_limit_config_t* limits[2];
|
||||
xce_conf4sched_t config;
|
||||
xce_group_config_t hg1 = {
|
||||
.name = "hg1",
|
||||
.members = { .count = 1, .data = (char*[]) { "hostA" } }
|
||||
};
|
||||
xce_group_config_t* hgroups[1] = { &hg1 };
|
||||
xce_group_config_t scope = {
|
||||
.members = { .count = 1, .data = (char*[]) { "hg1" } }
|
||||
};
|
||||
xce_limit_t* by_host = NULL;
|
||||
xce_limit_t* by_project = NULL;
|
||||
int rc;
|
||||
|
||||
fixture_init(&sched, &plugin);
|
||||
fixture_register_plugin_in_core(&(sched.core), &plugin);
|
||||
|
||||
rc = groupmgr_load_configs(sched.core.hgmgr, 1, hgroups);
|
||||
TEST_ASSERT_TRUE(rc >= 0);
|
||||
|
||||
limits[0] = make_limit_config("host_scoped");
|
||||
limits[0]->hosts = &scope;
|
||||
limits[1] = make_limit_config("project_scoped");
|
||||
limits[1]->projects = &scope;
|
||||
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.nlimits = 2;
|
||||
config.limits = limits;
|
||||
|
||||
rc = limit_update_config(&plugin, &config);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
{
|
||||
var limits_handle = limit_get_configured_limits(&(sched.core));
|
||||
iterator_t it;
|
||||
xce_limit_t* l = NULL;
|
||||
|
||||
TEST_ASSERT_NOT_NULL(limits_handle);
|
||||
rc = iter_make(&it, limits_handle);
|
||||
TEST_ASSERT_TRUE(rc >= 0);
|
||||
while ((rc = iter_next(&it, (void**)&l)) >= 0) {
|
||||
if (rc == RTTI_ITER_NO_DATA) {
|
||||
break;
|
||||
}
|
||||
if (strcmp(l->name, "host_scoped") == 0) {
|
||||
by_host = l;
|
||||
} else if (strcmp(l->name, "project_scoped") == 0) {
|
||||
by_project = l;
|
||||
}
|
||||
}
|
||||
iter_destroy(&it);
|
||||
}
|
||||
TEST_ASSERT_NOT_NULL(by_host);
|
||||
TEST_ASSERT_NOT_NULL(by_project);
|
||||
|
||||
/* host scope: hg1 is a host group, so its host matches */
|
||||
TEST_ASSERT_EQUAL_INT(1, match_limit(by_host, 0, "u1", "normal",
|
||||
"default", "hostA"));
|
||||
TEST_ASSERT_EQUAL_INT(0, match_limit(by_host, 0, "u1", "normal",
|
||||
"default", "hostB"));
|
||||
|
||||
/* project scope: hg1 is a literal project name, never the host group */
|
||||
TEST_ASSERT_EQUAL_INT(0, match_limit(by_project, 1, "u1", "normal",
|
||||
"hostA"));
|
||||
TEST_ASSERT_EQUAL_INT(1, match_limit(by_project, 1, "u1", "normal",
|
||||
"hg1"));
|
||||
|
||||
limits[0]->hosts = NULL;
|
||||
limits[1]->projects = NULL;
|
||||
free_limit_config(limits[0]);
|
||||
free_limit_config(limits[1]);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* group names are interned strings (makeString(), group.c) */
|
||||
xstring_table_init(139);
|
||||
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_duplicate_limit_name_no_double_free);
|
||||
RUN_TEST(test_distinct_limit_names_both_succeed);
|
||||
RUN_TEST(test_accessors_null_when_plugin_not_registered);
|
||||
RUN_TEST(test_get_configured_limits_handle_iterates_registered_limit);
|
||||
RUN_TEST(test_get_configured_limits_registered_with_zero_limits);
|
||||
return UNITY_END();
|
||||
RUN_TEST(test_limit_scopes_resolve_against_own_group_kind);
|
||||
rc = UNITY_END();
|
||||
|
||||
xstring_table_fini();
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue