diff --git a/src/configs/resources.yaml b/src/configs/resources.yaml index 4fc601b2..0c2fbe09 100644 --- a/src/configs/resources.yaml +++ b/src/configs/resources.yaml @@ -5,6 +5,7 @@ resources: type: numeric order: decrease consumable: true + builtin: true description: "job slot count (built-in)" # built-in: dynamic memory load index reported by workers @@ -12,6 +13,7 @@ resources: type: numeric interval: 60 consumable: true + builtin: true order: decrease description: "available memory in bytes (built-in dynamic load index; CLI display converts to a human-readable unit)" @@ -21,6 +23,7 @@ resources: type: numeric interval: 60 consumable: false + builtin: true order: increase description: "1-minute load average (built-in dynamic load index)" @@ -28,6 +31,7 @@ resources: type: numeric interval: 60 consumable: false + builtin: true order: increase description: "15-minute load average (built-in dynamic load index)" @@ -35,6 +39,7 @@ resources: type: numeric interval: 60 consumable: false + builtin: true order: increase description: "15-second load average (built-in dynamic load index)" @@ -42,6 +47,7 @@ resources: type: numeric interval: 60 consumable: false + builtin: true order: increase description: "cpu utilization" @@ -49,6 +55,7 @@ resources: type: numeric interval: 60 consumable: false + builtin: true order: increase description: "page ratio" @@ -56,6 +63,7 @@ resources: type: numeric interval: 60 consumable: false + builtin: true order: increase description: "login count" @@ -63,13 +71,15 @@ resources: type: numeric interval: 60 consumable: false + builtin: true order: increase description: "idle time" - + - name: swp type: numeric interval: 60 consumable: true + builtin: true order: decrease description: "swap size" @@ -77,13 +87,15 @@ resources: type: numeric interval: 60 consumable: false + builtin: true order: increase description: "io rate" - + - name: tmp type: numeric interval: 60 consumable: true + builtin: true order: decrease description: "available tmp disk space in bytes (built-in dynamic load index; CLI display converts to a human-readable unit)" @@ -91,34 +103,42 @@ resources: # controller/worker.c add_hostattrs_resvals() - name: type type: string + builtin: true description: "host OS/arch type (built-in host attribute)" - name: model type: string + builtin: true description: "host hardware model (built-in host attribute)" - name: cpuf type: numeric + builtin: true description: "CPU speed factor (built-in host attribute)" - name: maxcpus type: numeric + builtin: true description: "max CPU count (built-in host attribute)" - name: maxmem type: numeric + builtin: true description: "max memory in bytes (built-in host attribute; CLI display converts to a human-readable unit)" - name: maxswap type: numeric + builtin: true description: "max swap in bytes (built-in host attribute; CLI display converts to a human-readable unit)" - name: maxtmp type: numeric + builtin: true description: "max tmp disk space in bytes (built-in host attribute; CLI display converts to a human-readable unit)" - name: ndisks type: numeric + builtin: true description: "disk count (built-in host attribute)" # dynamic hostbased resource, external load index diff --git a/src/configs/schema/resources.schema.json b/src/configs/schema/resources.schema.json index b11620a4..06515aa0 100644 --- a/src/configs/schema/resources.schema.json +++ b/src/configs/schema/resources.schema.json @@ -12,6 +12,7 @@ "order": { "type": "string", "enum": ["increase", "decrease", "na"] }, "consumable": { "type": "boolean" }, "release": { "type": "boolean" }, + "builtin": { "type": "boolean" }, "description": { "type": "string" }, "resourcemap": { "type": "array", diff --git a/src/xceadmin/src/xceadmin/build.py b/src/xceadmin/src/xceadmin/build.py index edf33494..94799ec1 100644 --- a/src/xceadmin/src/xceadmin/build.py +++ b/src/xceadmin/src/xceadmin/build.py @@ -208,7 +208,9 @@ def _format_resource(r) -> str: ordtype = _RESOURCE_ORDTYPE_NAMES.get(r.ordtype, r.ordtype) consumable = bool(r.flags & mapping.RESOURCE_FLAG_CONSUMABLE) release = bool(r.flags & mapping.RESOURCE_FLAG_RELEASE) + builtin = bool(r.flags & mapping.RESOURCE_FLAG_BUILTIN) shared = bool(r.flags & mapping.RESOURCE_FLAG_SHARED) + external = bool(r.flags & mapping.RESOURCE_FLAG_EXTERNAL) parts = [f"name={r.name}", f"type={valtype}", f"order={ordtype}"] if r.desc: parts.append(f"desc={r.desc!r}") @@ -216,6 +218,13 @@ def _format_resource(r) -> str: parts.append(f"interval={r.interval}") parts.append(f"consumable={consumable}") parts.append(f"release={release}") + # builtin/external change the resource's semantics (whose values count, + # where they come from) -- always visible when set, never shown bare + # false: absent means the plain user-resource default. + if builtin: + parts.append("builtin=True") + if external: + parts.append("external=True") if shared: parts.append("shared=true") if len(r.distributions) > 0: diff --git a/src/xceadmin/src/xcecommon/mapping.py b/src/xceadmin/src/xcecommon/mapping.py index 709de22b..2637a373 100644 --- a/src/xceadmin/src/xcecommon/mapping.py +++ b/src/xceadmin/src/xcecommon/mapping.py @@ -64,12 +64,12 @@ reason -- never a silent drop. ``Resource`` (``resources.yaml``) ``valtype``/``ordtype``/``flags`` ARE mapped from YAML - ``type``/``order``/``consumable``/``release`` -- the encoding is - documented, just not in ``controller.capnp``/design.md: it lives in + ``type``/``order``/``consumable``/``release``/``builtin`` -- the encoding + is documented, just not in ``controller.capnp``/design.md: it lives in ``include/xce/types.h`` (``XCE_VAL_*``, ``xce_order_t``, - ``XCE_RESF_CONSUMABLE``/``XCE_RESF_RELEASE``) and is actively read at - runtime (e.g. ``scheduler.c`` copies these three fields straight out - of the parsed resource). See :data:`RESOURCE_VALTYPE`, + ``XCE_RESF_CONSUMABLE``/``XCE_RESF_RELEASE``/``XCE_RESF_BUILTIN``) and + is actively read at runtime (e.g. ``scheduler.c`` copies these three + fields straight out of the parsed resource). See :data:`RESOURCE_VALTYPE`, :data:`RESOURCE_ORDTYPE`, and the flag constants below for the exact values (also reused by ``xceadmin.build.dump_cluster_config`` to render these int-encoded fields back to their YAML names). ``idx`` and @@ -205,10 +205,16 @@ _XCE_SCALAR_FIELDS: tuple[tuple[str, str], ...] = ( RESOURCE_VALTYPE: dict[str, int] = {"string": 0, "numeric": 1, "boolean": 2} RESOURCE_ORDTYPE: dict[str, int] = {"increase": 0, "decrease": 1, "na": 2} -# resources.yaml ``consumable``/``release`` -> Resource.flags bits, mirroring -# include/xce/types.h's XCE_RESF_CONSUMABLE/XCE_RESF_RELEASE. +# resources.yaml ``consumable``/``release``/``builtin`` -> Resource.flags +# bits, mirroring include/xce/types.h's XCE_RESF_CONSUMABLE/ +# XCE_RESF_RELEASE/XCE_RESF_BUILTIN. RESOURCE_FLAG_CONSUMABLE = 0x80 RESOURCE_FLAG_RELEASE = 0x20 +# XCE_RESF_BUILTIN: the resource's values are produced by hardcoded worker/ +# controller reporters (resource_sync.c's load indices, worker.c's host +# attributes and slots) -- display/accounting code uses it to tell those +# from user-declared resources of the same name space. +RESOURCE_FLAG_BUILTIN = 0x01 # Set together on a resourcemap item whose location is exactly ["all"] # (cluster-wide static shared pool): XCE_RESF_SHARED | XCE_RESF_GLOBAL in @@ -421,6 +427,8 @@ def _build_resource(item: dict, target: "capnp._DynamicStructBuilder") -> None: flags |= RESOURCE_FLAG_CONSUMABLE if item.get("release"): flags |= RESOURCE_FLAG_RELEASE + if item.get("builtin"): + flags |= RESOURCE_FLAG_BUILTIN resourcemap = item.get("resourcemap") or [] if resourcemap: distributions = target.init("distributions", len(resourcemap)) diff --git a/src/xceadmin/tests/fixtures/full/configs/resources.yaml b/src/xceadmin/tests/fixtures/full/configs/resources.yaml index f12b9f82..22be4e85 100644 --- a/src/xceadmin/tests/fixtures/full/configs/resources.yaml +++ b/src/xceadmin/tests/fixtures/full/configs/resources.yaml @@ -12,6 +12,7 @@ resources: type: numeric interval: 60 consumable: false + builtin: true order: decrease description: "available memory in MB (built-in dynamic load index)" @@ -197,3 +198,16 @@ resources: - name: gstring description: string resource type: string + + # external-license-resources: cluster-wide license pool reported by + # the extres daemon (xceesd) -- config declares only the name/shape, + # the free counts come from the daemon's pushes + - name: extlic + description: externally reported license pool + type: numeric + interval: 0 + consumable: true + order: decrease + resourcemap: + - location: + - external diff --git a/src/xceadmin/tests/fixtures/full/expected.bin b/src/xceadmin/tests/fixtures/full/expected.bin index 4d0e67af..3fa1e0a7 100644 Binary files a/src/xceadmin/tests/fixtures/full/expected.bin and b/src/xceadmin/tests/fixtures/full/expected.bin differ diff --git a/src/xceadmin/tests/test_config_dump.py b/src/xceadmin/tests/test_config_dump.py index b2b66e55..3a94387f 100644 --- a/src/xceadmin/tests/test_config_dump.py +++ b/src/xceadmin/tests/test_config_dump.py @@ -49,6 +49,32 @@ def test_dump_lists_each_resource_with_decoded_type_and_order(full_cluster_bin): assert "consumable=False" in text +def test_dump_renders_builtin_and_external_flags(full_cluster_bin): + """The flag bits that change a resource's semantics must be visible in a + dump: builtin (worker/controller hardcode the values -- the repo fixture's + load indices and host attributes) and external (values come only from the + extres daemon's pushes -- the extlic pool shape). Both were silently + invisible before: every builtin resource dumped as a plain user resource, + and an external pool dumped as bare shared=true. + """ + text = build.dump_cluster_config(str(full_cluster_bin)) + mem_line = next(l for l in text.splitlines() if "name=mem type=numeric" in l) + assert "builtin=True" in mem_line + # a user-declared resource must not show a bare "builtin=" at all + assert "builtin=False" not in text + + import yaml as _yaml + configs = str(FIXTURES_DIR / "full" / "configs") + with open(configs + "/resources.yaml", encoding="utf-8") as fh: + names = [r["name"] for r in _yaml.safe_load(fh)["resources"] + if (r.get("resourcemap") or []) and + (r["resourcemap"][0].get("location") or []) == ["external"]] + assert names, "fixture must contain an external resource for this test" + for name in names: + line = next(l for l in text.splitlines() if f"name={name} " in l) + assert "external=True" in line, line + + def test_dump_renders_mxj_auto_detect_sentinel_not_raw_uint32(full_cluster_bin): """The exact bug that motivated this feature: hosts.yaml's `default` host has `max_slot: -1`, which wire-encodes as HOSTCONFIG_MXJ_AUTO_DETECT diff --git a/src/xceadmin/tests/test_mapping.py b/src/xceadmin/tests/test_mapping.py index 109da3f3..089e64d0 100644 --- a/src/xceadmin/tests/test_mapping.py +++ b/src/xceadmin/tests/test_mapping.py @@ -176,6 +176,29 @@ def test_map_list_section_resources_populates_mapped_fields_only(cluster_config) assert r0.lastReport == 0 +def test_map_list_section_resources_builtin_flag(cluster_config): + """``builtin: true`` (resources.yaml) -> XCE_RESF_BUILTIN (0x01) OR'd + on top of the other bits: the load indices (mem/io/r1m...) and host + attributes (type/model/maxmem...) are produced by hardcoded worker/ + controller reporters, and the flag tells them apart from user-declared + resources (the CLI's BUILTIN column renders it). Absent/false must + stay 0 -- the same convention as consumable/release. + """ + items = [ + {"name": "mem", "type": "numeric", "interval": 60, + "consumable": True, "builtin": True, "description": "memory"}, + {"name": "mylic", "type": "numeric", "consumable": True, + "builtin": False, "description": "user pool"}, + {"name": "plain", "type": "numeric"}, # builtin absent + ] + mapping.map_list_section(_spec_for("resources"), items, cluster_config) + + r0, r1, r2 = cluster_config.resources + assert r0.flags == 0x80 | 0x01 # CONSUMABLE | BUILTIN + assert r1.flags == 0x80 # explicit false -> no BUILTIN bit + assert r2.flags == 0 # absent -> no BUILTIN bit + + def test_map_list_section_noop_on_empty_or_missing_items(cluster_config): mapping.map_list_section(_spec_for("resources"), [], cluster_config) mapping.map_list_section(_spec_for("resources"), None, cluster_config) @@ -307,6 +330,18 @@ def test_map_list_section_resourcemap_real_repo_configs(cluster_config): for name in ("staticres1", "staticres2", "staticres3", "host_quota"): assert by_name[name].flags & (0x08 | 0x04) == 0 + # builtin:true resources (the worker load indices + host attributes the + # repo config declares) carry XCE_RESF_BUILTIN; user-declared shapes + # (staticres*, extlic) do not. + for name in ("slots", "mem", "r1m", "r15m", "r15s", "ut", "pg", "ls", "it", + "swp", "io", "tmp", "type", "model", "cpuf", "maxcpus", + "maxmem", "maxswap", "maxtmp", "ndisks"): + assert by_name[name].flags & 0x01 == 0x01, f"{name} missing BUILTIN" + for name in ("dynres1", "dynres2", "staticres1", "staticres2", "staticres3", + "staticres4", "res1", "sres1", "bigmem", "strres1", "strres2", + "host_quota", "gstring", "extlic"): + assert by_name[name].flags & 0x01 == 0, f"{name} unexpectedly BUILTIN" + # --------------------------------------------------------------------------- # map_list_section: users.yaml/hosts.yaml -> ugroups/hgroups (named groups)