Skip to content

Commit 5829fd5

Browse files
committed
refactor: ci and skills
Signed-off-by: thxCode <thxcode0824@gmail.com>
1 parent f72b23b commit 5829fd5

20 files changed

Lines changed: 1200 additions & 34 deletions
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
---
2+
name: gpustack-operator-xbuild-and-verify
3+
description: "Build and verify the GPUStack Operator's accelerator soft-slicing **builder stages** (`xbuild-ascend-cann-*` and `xbuild-nvidia-cuda-*` in `pack/gpustack-operator/Dockerfile`) end to end, either on the local docker host or on a remote accelerator host over ssh. Builds one stage via buildx `--target`, then runs numbered cases against the produced runtime. SCOPE — two backends: **Ascend (vcann-rt: `libvruntime.so` + `enpu-monitor`)** and **NVIDIA (HAMi-core: `libvgpu.so`)**. Ascend cases: (1) artifacts+linking [no NPU], (2) inject + `enpu-monitor`, (3) memory-quota enforcement. NVIDIA cases: (1) artifacts+linking [no GPU], (2) single-card inject + `nvidia-smi`/SM-limit, (3) multi-card per-device limits. The hardware cases need a real accelerator. Proactively offer this whenever a branch changes the Docker build flow — `pack/gpustack-operator/Dockerfile` or `pack/gpustack-operator/external/(ascend|nvidia)/**`. Examples: \"verify my Dockerfile build-stage change\", \"did the vcann-rt / HAMi-core build still link\", \"test the soft-slicing build on the 910B / 4090 host\", \"does enpu-monitor still work in a container\", \"does nvidia-smi show the sliced memory\", \"prove the memory slice is enforced on real hardware\"."
4+
allowed-tools: "Read, AskUserQuestion, Bash(bash .claude/skills/gpustack-operator-xbuild-and-verify/scripts/preflight.sh*), Bash(bash .claude/skills/gpustack-operator-xbuild-and-verify/scripts/build.sh*), Bash(bash .claude/skills/gpustack-operator-xbuild-and-verify/cases/ascend-case-1.sh*), Bash(bash .claude/skills/gpustack-operator-xbuild-and-verify/cases/ascend-case-2.sh*), Bash(bash .claude/skills/gpustack-operator-xbuild-and-verify/cases/ascend-case-3.sh*), Bash(bash .claude/skills/gpustack-operator-xbuild-and-verify/cases/nvidia-case-1.sh*), Bash(bash .claude/skills/gpustack-operator-xbuild-and-verify/cases/nvidia-case-2.sh*), Bash(bash .claude/skills/gpustack-operator-xbuild-and-verify/cases/nvidia-case-3.sh*), Bash(grep*), Bash(git diff*), Bash(git rev-parse*), Bash(ssh*), Bash(docker buildx*), Bash(docker images*), Bash(docker info*), Bash(command -v*)"
5+
model: sonnet
6+
---
7+
8+
# GPUStack Operator — accelerator xbuild & verify
9+
10+
Build one soft-slicing builder stage from `pack/gpustack-operator/Dockerfile` and verify the runtime it
11+
produces, on the local docker host or on a remote accelerator host over ssh. Two backends:
12+
13+
- **Ascend (vcann-rt).** `xbuild-ascend-cann-*``libvruntime.so` + `enpu-monitor`. Verifies artifacts/
14+
linking, the `npu_info.config` injection, and **real memory-quota enforcement** on a real NPU.
15+
- **NVIDIA (HAMi-core).** `xbuild-nvidia-cuda-*``libvgpu.so`. Verifies artifacts/linking, single-card
16+
injection (`nvidia-smi` shows the sliced VRAM + the SM/compute limit is applied), and **multi-card
17+
per-device limits** on real GPUs.
18+
19+
It is the build+runtime-contract counterpart to the cluster-level `gpustack-operator-e2e` (scheduling
20+
chain) and `gpustack-operator-chart-e2e` (chart). This is an evolving, e2e-style skill: extend the cases as
21+
the build flow grows.
22+
23+
## When to offer it
24+
Proactively suggest this skill when a branch changes the Docker build flow:
25+
```bash
26+
git diff --name-only origin/main...HEAD | grep -E 'pack/gpustack-operator/(Dockerfile|external/(ascend|nvidia)/)'
27+
```
28+
29+
## Runner model (local or remote)
30+
All scripts source `scripts/lib.sh` and run through one runner, selected by env:
31+
- `XB_MODE=local` — build & verify on this host.
32+
- `XB_MODE=ssh XB_HOST=user@host` — build & verify on a remote host. Files move via base64-over-ssh
33+
(never scp — a login banner corrupts it); a remote login banner is filtered from output.
34+
35+
The remote host is **never hardcoded** — always ask the user for it.
36+
37+
## Hard rules
38+
- **Never push images** — builds use `buildx --load` into the local/remote docker store only.
39+
- **Confirm before any remote build or container run** (they consume the host's accelerator/driver).
40+
Preflight and the build-artifact case (ASCEND-CASE 1 / NVIDIA-CASE 1) are safe once the user names the target.
41+
- Touch only what the skill creates (the `vcann-build:*` / `vgpu-build:*` image, `${XB_STAGE}` artifacts,
42+
`${XB_STAGE}/test` config/preload, the remote build context). Never modify the user's other resources.
43+
- The hardware cases require a **real accelerator** (local or the ssh host): ASCEND-CASE 2/3 need an NPU;
44+
NVIDIA-CASE 2 needs a GPU, NVIDIA-CASE 3 needs **≥ 2** GPUs. The two CASE-1 builds need only docker+buildx.
45+
46+
## Flow
47+
48+
1. **Discover targets.** List the builder stages and ask which to verify (multi-select):
49+
```bash
50+
grep -nE 'AS xbuild-(ascend-cann|nvidia-cuda)-' pack/gpustack-operator/Dockerfile
51+
```
52+
Ascend: `xbuild-ascend-cann-8-910b`, `-8-910c`, `-9-910b`, `-9-910c`, `-9-950`.
53+
NVIDIA: `xbuild-nvidia-cuda-12`, `-13`.
54+
55+
2. **Pick connection (AskUserQuestion).** Local, or ssh — and if ssh, the host. Set `XB_MODE`/`XB_HOST`.
56+
57+
3. **Preflight (read-only, confirm target first).**
58+
```bash
59+
XB_MODE=… XB_HOST=… bash .claude/skills/gpustack-operator-xbuild-and-verify/scripts/preflight.sh
60+
```
61+
docker+buildx must PASS to build. The hardware rows (`npu-smi`/ascend-runtime/`/dev/davinci*` and
62+
`nvidia-smi`/nvidia-runtime/`/dev/nvidia*`) WARN when absent — the matching hardware cases are then
63+
unavailable. If buildx is missing, the table prints the install one-liner.
64+
65+
4. **Build the chosen target (confirm).** `build.sh` infers the backend from the target prefix. Native on a
66+
matching-arch host (fast); cross-arch uses qemu.
67+
```bash
68+
XB_MODE=… XB_HOST=… bash .claude/skills/gpustack-operator-xbuild-and-verify/scripts/build.sh xbuild-nvidia-cuda-13
69+
```
70+
Produces `XB_IMAGE` (Ascend `vcann-build:<suffix>` / NVIDIA `vgpu-build:<suffix>`) and stages the
71+
artifacts under `XB_STAGE` (Ascend `/opt/enpu/vcann-rt`, NVIDIA `/opt/vgpu`). The built image is
72+
CANN/CUDA-based and doubles as the workload image for the hardware cases (`XB_WORKLOAD_IMAGE` defaults
73+
to it).
74+
75+
5. **Run cases.** Pass the same target; read each PASS/FAIL table — don't re-derive from raw logs.
76+
```bash
77+
# Ascend
78+
XB_MODE=… XB_HOST=… bash .../cases/ascend-case-1.sh xbuild-ascend-cann-8-910b
79+
XB_MODE=… XB_HOST=… XB_NPU=0 bash .../cases/ascend-case-2.sh xbuild-ascend-cann-8-910b
80+
XB_MODE=… XB_HOST=… XB_NPU=0 XB_MEM=1024 bash .../cases/ascend-case-3.sh xbuild-ascend-cann-8-910b
81+
# NVIDIA
82+
XB_MODE=… XB_HOST=… bash .../cases/nvidia-case-1.sh xbuild-nvidia-cuda-13
83+
XB_MODE=… XB_HOST=… XB_GPU=0 XB_MEM=4096 XB_SM=50 bash .../cases/nvidia-case-2.sh xbuild-nvidia-cuda-13
84+
XB_MODE=… XB_HOST=… XB_GPUS=0,1 XB_MEM=4096 bash .../cases/nvidia-case-3.sh xbuild-nvidia-cuda-13
85+
```
86+
87+
## Cases (locked titles)
88+
89+
### Ascend (`xbuild-ascend-cann-*`)
90+
| Case | Title | Needs NPU | Asserts |
91+
|---|---|---|---|
92+
| 1 | Build artifacts + linking | no | `libvruntime.so` (0644) + `enpu-monitor` (0755) exist; ELF arch == build platform; build linked (the `--allow-shlib-undefined` path); both `NEEDED` `libc_sec.so`+`libascendcl.so`; notes the weak UND `dcmi_*` syms |
93+
| 2 | Inject + enpu-monitor | yes | VDie-ID→`shm-id`; render `npu_info.config`; preload (libdcmi×2 + libvruntime); container `enpu-monitor` loads all 6 fields, initializes, and prints `Aicore Limit Quota`/`Memory Limit quota` matching the config |
94+
| 3 | Memory-quota enforcement | yes | injected HBM alloc capped at `memory-quota` (the `Out of memory! … quota:<bytes>` log); baseline (no inject) exceeds it |
95+
96+
### NVIDIA (`xbuild-nvidia-cuda-*`)
97+
| Case | Title | Needs GPU | Asserts |
98+
|---|---|---|---|
99+
| 1 | Build artifacts + linking | no | `libvgpu.so` (0644) exists; ELF arch == build platform; `NEEDED` `libcuda.so.1`+`libnvidia-ml.so.1` (hard deps the NVIDIA runtime injects — no weak-UND preload, contrast Ascend) |
100+
| 2 | Single-card inject + nvidia-smi | yes (1 GPU) | preload `libvgpu.so`; with `CUDA_DEVICE_MEMORY_LIMIT_0`+`CUDA_DEVICE_SM_LIMIT`, `nvidia-smi memory.total` == the limit (NVML hook); a CUDA probe logs `core utilization limit = <SM>` and `cuMemGetInfo` total == the limit (real CUDA-level enforcement) |
101+
| 3 | Multi-card per-device limits | yes (≥2 GPU) | each exposed card gets a **distinct** `CUDA_DEVICE_MEMORY_LIMIT_<i>` and the container's `nvidia-smi` reports each card's `memory.total` at its own limit (skips with WARN if too few GPUs) |
102+
103+
## Env knobs
104+
`XB_MODE`/`XB_HOST` (runner); `XB_PLATFORM` (default from target arch); `XB_IMAGE`/`XB_WORKLOAD_IMAGE`;
105+
`XB_STAGE` (Ascend `/opt/enpu/vcann-rt` | NVIDIA `/opt/vgpu`); `XB_REMOTE_CTX` (remote build-context dir).
106+
- Ascend: `XB_NPU`/`XB_CHIP` (card/chip, default 0); `XB_VNPU` (0); `XB_AICORE` (20); `XB_MEM` (1024 MB).
107+
- NVIDIA: `XB_GPU` (single-card index, 0); `XB_GPUS` (multi-card csv, `0,1`); `XB_MEM` (MiB, 4096);
108+
`XB_SM` (compute %, 50 / 30).
109+
110+
## References
111+
- `references/ascend-npu-info-config.md` — Ascend: the 6 config fields, VDie-ID→shm-id, allocator mapping.
112+
- `references/ascend-ld-preload-and-libdcmi.md` — Ascend activation via `/etc/ld.so.preload`; **why libdcmi must
113+
be preloaded** (weak dcmi syms); the `libc_sec`/CANN-image requirement.
114+
- `references/ascend-npu-smi-and-aicore.md` — Ascend: `npu-smi` shows the physical card; AICore-quota mechanism,
115+
the benign CANN-8.5.0 warnings, the unverified-throttle gap.
116+
- `references/nvidia-hami-core-vgpu.md` — NVIDIA: what `libvgpu.so` is, the env+mount injection contract, the
117+
one-CUDA-major-per-container rule, HAMi-core knobs.
118+
- `references/nvidia-smi-and-sm-limit.md` — NVIDIA: memory limit is directly visible in `nvidia-smi` (NVML
119+
hook); the SM/compute limit is a time-slice throttle (HAMi log / under-load only); CUDA-13 probe gotchas.
120+
- `references/troubleshooting.md` — both backends: scp banner, buildx-missing, Ascend link/segfault/hgemm,
121+
NVIDIA runtime/preload/stale-cache/cuCtxCreate-v4/stub-lib/SM-visibility.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/usr/bin/env bash
2+
#
3+
# ASCEND-CASE 1 — Build artifacts + linking (no NPU; runs on any docker host)
4+
#
5+
# ascend-case-1.sh <TARGET>
6+
#
7+
# Assumes build.sh already built XB_IMAGE for <TARGET>. A successful build IS the
8+
# link test: the enpu-monitor executable only links because build-libvnpu.sh passes
9+
# -Wl,--allow-shlib-undefined (the vendor .so cross-refs — HAL drv*, ErrorManager::* —
10+
# resolve at runtime, not link time). This case then inspects the produced artifacts:
11+
# - /out/lib/libvruntime.so exists, mode 0644
12+
# - /out/tools/enpu-monitor exists, mode 0755
13+
# - both ELF machine == the build platform's arch
14+
# - both NEEDED libc_sec.so + libascendcl.so (the runtime deps; libc_sec is the
15+
# securec/libboundscheck library — see references/ascend-ld-preload-and-libdcmi.md)
16+
# - enpu-monitor carries WEAK UND dcmi_* symbols (informational: that is why
17+
# libdcmi must be preloaded at runtime — ASCEND-CASE 2 covers it)
18+
#
19+
# Prints a STATUS | CHECK | DETAIL table; exits non-zero on any FAIL.
20+
set -uo pipefail
21+
HERE="$(cd "$(dirname "$0")" && pwd)"
22+
# shellcheck source=../scripts/lib.sh
23+
. "${HERE}/../scripts/lib.sh"
24+
25+
TARGET="${1:?usage: ascend-case-1.sh <TARGET>}"
26+
XB_IMAGE="${XB_IMAGE:-vcann-build:${TARGET#xbuild-ascend-cann-}}"
27+
XB_PLATFORM="${XB_PLATFORM:-}"
28+
29+
# Expected ELF machine from the platform; if unset, derive from the target host arch.
30+
if [ -z "${XB_PLATFORM}" ]; then
31+
a="$(xrun 'uname -m' | tr -d '[:space:]')"
32+
case "${a}" in x86_64) XB_PLATFORM=linux/amd64 ;; aarch64) XB_PLATFORM=linux/arm64 ;; esac
33+
fi
34+
case "${XB_PLATFORM}" in
35+
linux/amd64) EXPECT_MACHINE="X86-64" ;;
36+
linux/arm64) EXPECT_MACHINE="AArch64" ;;
37+
*) EXPECT_MACHINE="" ;;
38+
esac
39+
40+
echo "# ASCEND-CASE 1 — ${XB_IMAGE} (expect ${EXPECT_MACHINE:-any}) on $(xtarget_desc)"
41+
42+
out="$(xsh XB_IMAGE="${XB_IMAGE}" EXPECT_MACHINE="${EXPECT_MACHINE}" <<'PAYLOAD'
43+
set -u
44+
docker run --rm -i "${XB_IMAGE}" bash -s "${EXPECT_MACHINE}" <<'INNER'
45+
set -u
46+
EXPECT="${1:-}"
47+
row(){ printf '%s | %s | %s\n' "$1" "$2" "$3"; }
48+
fails=0
49+
LV=/out/lib/libvruntime.so
50+
EM=/out/tools/enpu-monitor
51+
chk_mode(){ local f="$1" want="$2" name="$3" m; if [ -f "$f" ]; then m=$(stat -c '%a' "$f"); [ "$m" = "$want" ] && row PASS "$name mode $want" "$m" || { row FAIL "$name mode $want" "$m"; fails=$((fails+1)); }; else row FAIL "$name exists" missing; fails=$((fails+1)); fi; }
52+
chk_mode "$LV" 644 "libvruntime.so"
53+
chk_mode "$EM" 755 "enpu-monitor"
54+
for f in "$LV" "$EM"; do
55+
mach=$(readelf -h "$f" 2>/dev/null | awk -F: '/Machine/{print $2}' | xargs)
56+
if [ -z "$EXPECT" ] || echo "$mach" | grep -q "$EXPECT"; then row PASS "arch $(basename $f)" "$mach"; else row FAIL "arch $(basename $f)==$EXPECT" "$mach"; fails=$((fails+1)); fi
57+
done
58+
for f in "$LV" "$EM"; do
59+
nd=$(readelf -d "$f" 2>/dev/null | grep NEEDED)
60+
for need in libc_sec.so libascendcl.so; do
61+
echo "$nd" | grep -q "$need" && row PASS "NEEDED $need ($(basename $f))" ok || { row FAIL "NEEDED $need ($(basename $f))" absent; fails=$((fails+1)); }
62+
done
63+
done
64+
w=$(readelf -sW "$EM" 2>/dev/null | grep -cE "WEAK +DEFAULT +UND +dcmi" || true)
65+
row INFO "enpu-monitor weak UND dcmi syms" "${w} (=> libdcmi must be preloaded at runtime, see ASCEND-CASE 2)"
66+
echo "FAILS=${fails}"
67+
INNER
68+
PAYLOAD
69+
)"
70+
echo "${out}"
71+
echo "${out}" | grep -q 'FAILS=0' && { echo "ASCEND-CASE 1: PASS"; exit 0; } || { echo "ASCEND-CASE 1: FAIL"; exit 1; }
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/usr/bin/env bash
2+
#
3+
# ASCEND-CASE 2 — Inject + enpu-monitor (needs a real Ascend NPU host)
4+
#
5+
# ascend-case-2.sh [TARGET]
6+
#
7+
# Reproduces the GPUStack soft-slicing injection by hand and confirms vcann-rt
8+
# initializes and reports the configured quota inside a container:
9+
# 1. read the card VDie-ID (npu-smi info -t board) -> shm-id (spaces -> '-')
10+
# 2. render npu_info.config (0644) — the same 6 fields the Ascend allocator emits
11+
# (renderNPUInfoConfig in pkg/devicemanager/allocator/ascend/deviceplugin.go)
12+
# 3. write ld.so.preload listing the two host-injected libdcmi paths BEFORE
13+
# libvruntime.so (libdcmi must be loaded or the weak dcmi_* symbols segfault —
14+
# see references/ascend-ld-preload-and-libdcmi.md)
15+
# 4. docker run (ascend runtime, ASCEND_VISIBLE_DEVICES) the staged enpu-monitor
16+
# 5. assert: all 6 config fields loaded, "Successfully to initialize", and the
17+
# enpu-monitor Aicore/Memory quota lines match the config
18+
#
19+
# Env: XB_WORKLOAD_IMAGE (default XB_IMAGE; must be CANN-based), XB_STAGE
20+
# (/opt/enpu/vcann-rt), XB_NPU (0), XB_CHIP (0), XB_VNPU (0), XB_AICORE (20),
21+
# XB_MEM (1024 MB). Prints a STATUS|CHECK|DETAIL table; non-zero on FAIL.
22+
set -uo pipefail
23+
HERE="$(cd "$(dirname "$0")" && pwd)"
24+
# shellcheck source=../scripts/lib.sh
25+
. "${HERE}/../scripts/lib.sh"
26+
27+
TARGET="${1:-}"
28+
IMG="${XB_WORKLOAD_IMAGE:-${XB_IMAGE:-}}"
29+
[ -z "${IMG}" ] && [ -n "${TARGET}" ] && IMG="vcann-build:${TARGET#xbuild-ascend-cann-}"
30+
[ -n "${IMG}" ] || { echo "case-2: pass a TARGET (e.g. xbuild-ascend-cann-8-910b) or set XB_WORKLOAD_IMAGE"; exit 2; }
31+
32+
echo "# ASCEND-CASE 2 — inject + enpu-monitor (image ${IMG}, npu ${XB_NPU:-0}) on $(xtarget_desc)"
33+
34+
out="$(xsh \
35+
IMG="${IMG}" STAGE="${XB_STAGE:-/opt/enpu/vcann-rt}" \
36+
NPU="${XB_NPU:-0}" CHIP="${XB_CHIP:-0}" VNPU="${XB_VNPU:-0}" \
37+
AICORE="${XB_AICORE:-20}" MEM="${XB_MEM:-1024}" <<'PAYLOAD'
38+
set -u
39+
row(){ printf '%s | %s | %s\n' "$1" "$2" "$3"; }
40+
fails=0
41+
42+
vdie="$(npu-smi info -t board -i "${NPU}" -c "${CHIP}" 2>/dev/null | awk -F: '/VDie ID/{print $2}' | xargs)"
43+
if [ -n "${vdie}" ]; then shmid="$(echo "${vdie}" | tr ' ' '-')"; row PASS "VDie-ID -> shm-id" "${shmid}";
44+
else shmid="UNKNOWN-VDIE"; row WARN "VDie-ID" "not found via npu-smi; using ${shmid}"; fi
45+
46+
mkdir -p "${STAGE}/test"
47+
cfg="${STAGE}/test/npu_info.config"
48+
printf 'physical-npu-id=%s\nvirtual-npu-id=%s\naicore-quota=%s\nmemory-quota=%s\nshm-id=%s\nscheduling-policy=2\n' \
49+
"${NPU}" "${VNPU}" "${AICORE}" "${MEM}" "${shmid}" > "${cfg}"
50+
chmod 0644 "${cfg}"
51+
[ "$(stat -c '%a' "${cfg}")" = 644 ] && row PASS "npu_info.config mode 0644" ok || { row FAIL "npu_info.config mode 0644" "$(stat -c '%a' "${cfg}")"; fails=$((fails+1)); }
52+
53+
pre="${STAGE}/test/ld.so.preload"
54+
printf '/usr/local/dcmi/libdcmi.so\n/usr/local/Ascend/driver/lib64/driver/libdcmi.so\n/opt/enpu/vcann-rt/lib/libvruntime.so\n' > "${pre}"
55+
chmod 0644 "${pre}"
56+
57+
log="$(docker run --rm --runtime=ascend -e ASCEND_VISIBLE_DEVICES="${NPU}" -e ENPU_LOG_LEVEL=3 \
58+
-v "${STAGE}/lib/libvruntime.so:/opt/enpu/vcann-rt/lib/libvruntime.so:ro" \
59+
-v "${STAGE}/tools/enpu-monitor:/opt/enpu/vcann-rt/tools/enpu-monitor:ro" \
60+
-v "${cfg}:/etc/enpu/vcann-rt/npu_info.config:ro" \
61+
-v "${pre}:/etc/ld.so.preload:ro" \
62+
-v /dev/shm:/dev/shm \
63+
"${IMG}" /opt/enpu/vcann-rt/tools/enpu-monitor 2>&1)"
64+
65+
n="$(echo "${log}" | grep -c 'Success to load config')"
66+
[ "${n}" -ge 6 ] && row PASS "config fields loaded" "${n}/6" || { row FAIL "config fields loaded" "${n}/6"; fails=$((fails+1)); }
67+
echo "${log}" | grep -q 'Successfully to initialize' && row PASS "vcann-rt initialize" ok || { row FAIL "vcann-rt initialize" "no init log"; fails=$((fails+1)); }
68+
echo "${log}" | grep -q 'Successfully to initialize all module' && row PASS "all-module init" ok || row WARN "all-module init" "only vnpu-device init seen (enpu-monitor standalone)"
69+
ac="$(echo "${log}" | awk -F: '/Aicore Limit Quota/{gsub(/ /,"",$2);print $2}')"
70+
[ "${ac}" = "${AICORE}" ] && row PASS "Aicore Limit Quota" "${ac}" || { row FAIL "Aicore Limit Quota == ${AICORE}" "${ac:-none}"; fails=$((fails+1)); }
71+
mq="$(echo "${log}" | awk -F: '/Memory Limit quota/{gsub(/ /,"",$2);print $2}')"
72+
[ "${mq}" = "${MEM}" ] && row PASS "Memory Limit quota(MB)" "${mq}" || { row FAIL "Memory Limit quota == ${MEM}" "${mq:-none}"; fails=$((fails+1)); }
73+
74+
echo "--- enpu-monitor output ---"
75+
echo "${log}" | grep -iE 'Quota|Usage|initialize all module' || true
76+
echo "FAILS=${fails}"
77+
PAYLOAD
78+
)"
79+
echo "${out}"
80+
echo "${out}" | grep -q 'FAILS=0' && { echo "ASCEND-CASE 2: PASS"; exit 0; } || { echo "ASCEND-CASE 2: FAIL"; exit 1; }

0 commit comments

Comments
 (0)