6.1 KiB
Research: resourcemap / distributions / hostBased
Status: scope decision pending — do not generate requirements.md until a scope option below is chosen
What triggered this spec
xceadmin config build warns resources.yaml: field 'resourcemap' on '<name>' is schema-valid but has no ClusterConfig destination -- ignored for every resource entry that uses resourcemap. The initial framing (a quick capnp-field-uncomment-and-wire task) turned out to understate the real scope once the C-side consumers were traced — see findings below.
Findings (verified against code, file:line cited)
res_distribution_t(include/xce/types.h:127-131):{value: xce_val_t*, nhosts: int, hosts: char**}— maps 1:1 to oneresourcemapYAML entry{value, location: [...]}. Commented-out capnp counterpart:src/schema/controller.capnp:195-198,208-209.xce_resource_t.hbased_definedis typedxce_val_t*(a value, not a bool flag) — plausibly a fallback/default value for hosts not covered by any explicitdistributionsentry (matches thelocation: [default]sentinel used bydynres1/strres1/staticres3/host_quota). Inference only — nothing in the codebase reads this field to confirm.- The fields are dead on the C side too, not just uncommented in capnp.
resobj_init()(src/lib/resobj.c:37-51) explicitly zeroesdst->distributions/dst->hbased_definedwith/* FIXME: */comments, even under a hypothetical populated input.scheduler.c:787-813's resource update handler copies onlyvaltype/ordtype/flags/interval, with a comment explaicitly excluding these two as "deep-structs." No other file in scheduler/, worker/, controller/, lib/ readsres_distribution_t,->distributions, or->hbased_definedat all (repo-wide grep, zero other hits). - A separate, fully-live per-host resource-value pipeline already exists, independent of
resourcemap, viahosts.yaml's host-levelresource: [{name, value}]list. Full trace:mapping.py:_build_host_config→HostConfig.resources(capnp) → controllerapply_join_state()→mark_host_res()(src/controller/worker.c:163-221) →update_val_res()writesw->host.resvals[idx]→ pushed to scheduler viacollect_resval()/Worker.resvals→schedobj.c:286-304zarray_put(&(worker->resvals), ...)→ read bysched_check_host_satisfy_()(src/scheduler/src/resource.c:330,357-379) during job placement. mark_host_res()(worker.c:190-193) actively rejects a resource flaggedXCE_RESF_SHARED:if (r->flags & XCE_RESF_SHARED) { ERROR(...); return -EINVAL; }. So "shared pool" and "per-host static value" are mutually exclusive today, by construction.XCE_RESF_SHARED/GLOBALpooling machinery (sched->sharedInst,sched_check_shared_satisfy_(),src/scheduler/src/resource.c:232-282) is real and live, but exclusively serves job-limit virtual resources (src/lib/xcelimit.c:399-404'sadd_vres()), never a physicalresources.yamlresource. The scheduler plumbing for "N consumers draw from one pooled value" is architecturally reusable, just not reachable fromresourcemaptoday.interval/dynamic-resource semantics have zero enforcement logic anywhere —last_reportis never read;intervalis only copied, never acted on. The only real dynamic-reporting mechanism issrc/worker/src/resource_sync.c's 11 hardcoded built-in load indices (init_builtin_load_indices(), lines 59-94). A plugin hook exists to register a custom dynamic resource (resource_sync_register_load_index(), line 104) but nothing calls it from any YAML-driven path — a user-defined dynamic resource (dynres1/dynres2-style) has no live way to ever acquire a value today.src/configs/resources.yaml's own example comments are internally inconsistent about "share" semantics:staticres1(oneresourcemapentry, two hosts inlocation:) says "host1 and host2 share the 100 res";staticres2(two separate entries, one host each) says "each of host1 and host2 shares the 100 res" — opposite shapes, same word. Not a reliable semantic source as-is.
Three candidate scopes (sizes verified against the above, not guessed)
-
Static replication to N hosts (
staticres3-style: oneresourcemapdeclaration → the same static value written into N named hosts' per-hostresvals, reusing the existingmark_host_res()/update_val_res()path). Pure DRY/ergonomics win — functionally redundant with just repeating{name, value}across Nhosts.yamlhost entries, sincehosts.yamlalready does the "one host, one static value" case live end-to-end. Small: capnp field un-comment + mapping.py translation + a controller-side loop over the host list at config-load/push time. No scheduler changes, no new runtime concept. -
Static replication + shared pool (adds
staticres1/staticres4-style: N hosts collectively draw down ONE pooled value). Requires: allowingXCE_RESF_SHAREDon a physical resource (currentlymark_host_resrefuses this), wiringsched->sharedInstfor resources.yaml-sourced resources (today limits-only), and defining job-placement semantics for a shared physical resource (distinct from the existing shared limit semantics — a resource being consumed to zero is a scheduling constraint, not a rejection). Real new capability, touches job-placement code. -
All three, plus dynamic per-host custom resource reporting (
dynres1/dynres2-style). Needs a new worker-side mechanism: something must callresource_sync_register_load_index()(or an equivalent) per YAML-declared dynamic resource, decide how/when it's collected per host, and thread it into the sameresvalspipewith proper interval-based refresh — none of which exists today even as a partial scaffold. Largest scope.
Decision
Not yet made — user chose to record the options and decide later rather than commit now. Do not proceed to requirements.md generation until one of the three scopes (or a variant) is chosen. When resumed, re-run /kiro-spec-requirements resource-distribution-mapping with the chosen scope stated explicitly, since the EARS requirements (and everything downstream) differ substantially by option.