feat(xceadmin): add 'config dump' command and size-string thresholds

Adds `xceadmin config dump CLUSTER_BIN_PATH`, the reverse of `config
build` -- decodes a serialized ClusterConfig back to human-readable text
so a built config can be inspected without a throwaway pycapnp script.
Motivated directly by how bug-1556 (hosts.yaml's max_slot: -1 auto-detect
sentinel silently becoming mxj=0) was actually found: by hand-decoding a
live cluster.bin. The dump renders the same sentinel as
"max_slot=-1 (auto-detect from CPU count)", not the raw uint32.

Also adds parse_size_or_number() to xcecommon.mapping so a queue/host's
sched_stop threshold on a byte-valued resource (maxmem/mem/tmp/maxswap/
maxtmp) can be written as a human-friendly size string ("500MB", "2GB")
instead of a raw byte count; a plain number still passes through
unchanged for non-byte resources like load averages. hosts.schema.json
and queues.schema.json's sched_stop schema gains a matching SizeOrNumber
type. dump_cluster_config() humanizes the same byte-valued thresholds
back on the way out (_format_bytes_human).

New ClusterBinFileError covers every local cluster.bin read/decode
failure mode without letting a raw OSError/capnp.KjException escape the
CLI. RESOURCE_VALTYPE/RESOURCE_ORDTYPE/RESOURCE_FLAG_* made public in
mapping.py so dump_cluster_config can reuse the same int<->name tables
build_cluster_config already uses, keeping both directions in sync by
construction.

256 xceadmin tests pass (incl. new test_config_dump.py and the
parse_size_or_number/SizeOrNumber cases in test_mapping.py/test_cli.py),
verified via `xce-build.sh xceadmin`'s full pinned out-of-tree install
+ pytest run before this commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-08-16 03:56:52 +00:00
parent d479b70764
commit 3afe3d5549
Signed by: rshen
SSH key fingerprint: SHA256:8Vf2pZ3h6rJa7EhGK+WaAQ9Mta/apZvteW6g9U/WKKk
9 changed files with 560 additions and 19 deletions

View file

@ -68,8 +68,8 @@
"additionalProperties": false,
"properties": {
"resname": { "type": "string" },
"sched": { "type": "number" },
"stop": { "type": "number" }
"sched": { "$ref": "#/$defs/SizeOrNumber" },
"stop": { "$ref": "#/$defs/SizeOrNumber" }
},
"required": ["resname"],
"anyOf": [
@ -77,6 +77,13 @@
{ "required": ["stop"] }
]
},
"SizeOrNumber": {
"description": "A plain number (the resource's own unit, e.g. a load average), or a size string with a unit suffix (e.g. \"500MB\", \"2GB\") for a byte-valued resource -- converted to bytes when the config is parsed.",
"oneOf": [
{ "type": "number" },
{ "type": "string", "pattern": "^[+-]?[0-9]+(\\.[0-9]+)?\\s*[A-Za-z]+$" }
]
},
"HostGroup": {
"type": "object",
"additionalProperties": false,

View file

@ -34,8 +34,8 @@
"additionalProperties": false,
"properties": {
"resname": { "type": "string" },
"sched": { "type": "number" },
"stop": { "type": "number" }
"sched": { "$ref": "#/$defs/SizeOrNumber" },
"stop": { "$ref": "#/$defs/SizeOrNumber" }
},
"required": ["resname"],
"anyOf": [
@ -43,6 +43,13 @@
{ "required": ["stop"] }
]
},
"SizeOrNumber": {
"description": "A plain number (the resource's own unit, e.g. a load average), or a size string with a unit suffix (e.g. \"500MB\", \"2GB\") for a byte-valued resource -- converted to bytes when the config is parsed.",
"oneOf": [
{ "type": "number" },
{ "type": "string", "pattern": "^[+-]?[0-9]+(\\.[0-9]+)?\\s*[A-Za-z]+$" }
]
},
"FairShare": {
"type": "object",
"additionalProperties": false,