2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-21 23:28:19 +00:00
Files
xcat-core/xCAT-test/bats/genesis_openembedded_hardware.bats
T
Daniel Hilst c6fc094c93 fix(xcat-core): test Genesis shell and C code by running it, not from Perl
The Perl tests of the OpenEmbedded Genesis image ran its shell scripts
and C programs through system(), qx, pipes and fork, wrote fake
commands through copied read_file and write_file helpers, and matched
regexes against shell and C source. Many rejections checked only a
non-zero exit, so an earlier, unrelated failure also passed. The
quality gate judged 91% of their 762 checks bad.

The shell and C checks move to bats in xCAT-test/bats. The C programs
are compiled there and run: the plain console, test drivers for the
shared status view and the shell launcher, and genesis-udp-send against
a loopback listener. Each rejection now matches the message of its own
guard. The .t files keep the artifact checks (kas, recipes, kernel
fragments, units, README), read with XCAT::Test::File. Test files left
with no checks are deleted.

Claim map (file -> where its claims went):
- go_xcat_uninstall_installed_only.t -> go_xcat_uninstall_installed_only.bats
- genesis_readme_paths.t, genesis_base_sources_staged.t -> kept in the
  .t (README read by slurp_repo_file, tarball read by Archive::Tar)
- genesis_openembedded_{export,hardware,runtime}.t -> the bats file of
  the same name
- genesis_openembedded_{bmcsetup,metrics,extensions,qeth,actions,
  discovery}.t -> scripts to the bats file of the same name, recipes
  and units kept in the .t
- genesis_openembedded_console.t -> built and run checks, state.c,
  plain_ui.c and shell.c to genesis_openembedded_console.bats; Newt
  source checks kept in the .t
- genesis_openembedded.t -> oe/build to genesis_openembedded_build.bats;
  shell matches to the runtime, hardware and extensions bats; console
  matches to the console bats or the console .t; artifacts kept

Red proof: every moved or new test was run against a mutated copy of
the code it covers and went red, for example: rpm -q not checked,
tar without --mtime, VEX report not exported, support dir after PATH,
digest check off, layer2 forced, wildcard device allowed, arguments
eval'd, tcp probe skipped, SOURCE_PORT 302, AF_INET only, stage timer
compared, launch error ignored, kas run twice, status written in place.
One mutation stayed green: tar without --owner=0 when the test runs as
root.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
2026-09-24 22:00:28 -03:00

265 lines
9.5 KiB
Bash

#!/usr/bin/env bats
#
# genesis-hardware runs in the OpenEmbedded Genesis image. It lists hardware providers from
# their JSON manifests, runs a provider capability, and writes an audit trail for each
# destructive change. The nvme, mstflint and iprutils providers wrap one tool each. The
# providers, the tools and the sysfs trees here are scratch files.
load 'helpers/shell_source'
HW_FILES='xCAT-genesis-base/oe/meta-xcat-genesis/recipes-core/xcat-genesis-hardware-control/files'
setup()
{
command -v jq >/dev/null || skip 'jq is required'
DISPATCHER="$(require_repo_file "$HW_FILES/genesis-hardware")"
root="$BATS_TEST_TMPDIR"
bin="$root/bin"
manifests="$root/manifests"
executables="$root/providers"
audit="$root/audit/hardware.jsonl"
request="$root/request.json"
mkdir -p "$bin" "$manifests" "$executables"
cat >"$executables/mock" <<'SH'
#!/bin/sh
case "$1" in
probe)
printf '%s\n' '{"detected":true,"devices":["controller0"]}'
;;
run)
case "$2" in
storage.inventory) printf '%s\n' '{"devices":["controller0"]}' ;;
storage.array.create) printf '{"created":"%s"}\n' "$3" ;;
storage.array.delete) printf '%s\n' 'delete failed' >&2; exit 9 ;;
storage.logs) printf '%s\n' 'not-json' ;;
*) exit 8 ;;
esac
;;
*) exit 7 ;;
esac
SH
chmod 0755 "$executables/mock"
jq -n '{
capabilities: [
{destructive: false, name: "storage.inventory"},
{destructive: false, name: "storage.logs"},
{destructive: true, name: "storage.array.create"},
{destructive: true, name: "storage.array.delete"}
],
kind: "storage", name: "mock", schema: 1, version: "1.0"
}' >"$manifests/mock.json"
printf '{"level":"1"}\n' >"$request"
export PATH="$bin:$PATH"
export XCAT_GENESIS_HARDWARE_AUDIT="$audit"
export XCAT_GENESIS_PROVIDER_DIR="$manifests"
export XCAT_GENESIS_PROVIDER_EXEC_DIR="$executables"
export XCAT_GENESIS_PROVIDER_TIMEOUT=5
}
hardware()
{
run /bin/bash "$DISPATCHER" "$@"
}
create_array()
{
hardware run mock storage.array.create "$@"
}
audit_phases()
{
jq -r .phase "$audit" | paste -sd' '
}
@test "the provider list returns the manifest" {
hardware providers
[ "$status" -eq 0 ]
[ "$(jq -r '.providers[0].name' <<<"$output")" = mock ]
}
@test "the provider capabilities are returned" {
hardware capabilities mock
[ "$status" -eq 0 ]
[ "$(jq '.capabilities | length' <<<"$output")" -eq 4 ]
}
@test "a provider probe returns the detected hardware" {
hardware probe mock
[ "$status" -eq 0 ]
[ "$(jq '.probes[0].result.detected' <<<"$output")" = true ]
}
@test "a read-only capability succeeds, is normalized, and is not audited as a change" {
hardware run mock storage.inventory
[ "$status" -eq 0 ]
[ "$(jq -r '.result.devices[0]' <<<"$output")" = controller0 ]
[ ! -e "$audit" ]
}
@test "a destructive capability requires a task identity, an exact device and a request" {
create_array
[ "$status" -ne 0 ]
[[ "$output" == *'destructive capability requires a task identity'* ]]
create_array --task-id task-1 --request "$request"
[ "$status" -ne 0 ]
[[ "$output" == *'destructive capability requires an exact device identity'* ]]
create_array --task-id task-1 --device-id controller0
[ "$status" -ne 0 ]
[[ "$output" == *'destructive capability requires a request file'* ]]
create_array --task-id task-1 --device-id all --request "$request"
[ "$status" -ne 0 ]
[[ "$output" == *'destructive capability requires an exact device identity'* ]]
}
@test "an authorized destructive capability succeeds with a closed audit trail" {
create_array --task-id task-1 --device-id controller0 --request "$request"
[ "$status" -eq 0 ]
[ "$(jq -r '.result.created' <<<"$output")" = controller0 ]
[ "$(audit_phases)" = 'started completed' ]
[ "$(head -n1 "$audit" | jq -r .task_id)" = task-1 ]
head -n1 "$audit" | jq -r .request_sha256 | grep -Eqx '[0-9a-f]{64}'
}
@test "a provider failure is returned, and the failed change has a closed audit trail" {
create_array --task-id task-1 --device-id controller0 --request "$request"
hardware run mock storage.array.delete --task-id task-2 --device-id controller0 --request "$request"
[ "$status" -ne 0 ]
[[ "$output" == *'provider mock failed capability storage.array.delete'* ]]
[ "$(audit_phases)" = 'started completed started failed' ]
}
@test "invalid provider JSON and an undeclared capability are rejected" {
hardware run mock storage.logs
[ "$status" -ne 0 ]
[[ "$output" == *'provider mock returned invalid JSON'* ]]
hardware run mock storage.unknown
[ "$status" -ne 0 ]
[[ "$output" == *'provider mock does not support storage.unknown'* ]]
}
@test "a linked or invalid provider manifest is rejected" {
# The target is a valid manifest for "linked", so only the link itself is wrong.
jq '.name = "linked"' "$manifests/mock.json" >"$root/linked.json"
ln -s "$root/linked.json" "$manifests/linked.json"
hardware providers
[ "$status" -ne 0 ]
[[ "$output" == *'invalid provider manifest: linked'* ]]
rm "$manifests/linked.json"
printf '{}\n' >"$manifests/invalid.json"
hardware providers
[ "$status" -ne 0 ]
[[ "$output" == *'invalid provider manifest: invalid'* ]]
}
@test "a linked provider executable is rejected" {
mv "$executables/mock" "$executables/mock.real"
ln -s "$executables/mock.real" "$executables/mock"
hardware probe mock
[ "$status" -ne 0 ]
[[ "$output" == *'provider executable not found: mock'* ]]
}
@test "a linked hardware request is rejected" {
ln -s "$request" "$root/linked-request.json"
create_array --task-id task-3 --device-id controller0 --request "$root/linked-request.json"
[ "$status" -ne 0 ]
[[ "$output" == *"invalid request file: $root/linked-request.json"* ]]
}
@test "an invalid provider timeout is rejected" {
XCAT_GENESIS_PROVIDER_TIMEOUT=0 hardware providers
[ "$status" -ne 0 ]
[[ "$output" == *'invalid provider timeout'* ]]
}
@test "a linked audit lock is rejected, and its target is untouched" {
mkdir -p "$(dirname "$audit")"
printf 'unchanged\n' >"$root/lock-target"
ln -s "$root/lock-target" "$audit.lock"
create_array --task-id task-3 --device-id controller0 --request "$request"
[ "$status" -ne 0 ]
[[ "$output" == *'invalid hardware audit lock'* ]]
[ "$(cat "$root/lock-target")" = unchanged ]
}
@test "the NVMe provider returns exact controllers and queries the selected one" {
provider="$(require_repo_file "$HW_FILES/provider-nvme")"
printf '#!/bin/sh\nprintf "{\\"arguments\\":\\"%%s\\"}\\n" "$*"\n' >"$bin/nvme"
chmod 0755 "$bin/nvme"
mkdir -p "$root/sys/class/nvme/nvme0" "$root/dev"
: >"$root/dev/nvme0"
export XCAT_GENESIS_DEV_DIR="$root/dev" XCAT_GENESIS_SYS_CLASS_NVME="$root/sys/class/nvme"
run /bin/bash "$provider" probe
[ "$status" -eq 0 ]
[ "$(jq -r '.devices[0]' <<<"$output")" = nvme0 ]
run /bin/bash "$provider" run storage.health nvme0 -
[ "$status" -eq 0 ]
jq -r .arguments <<<"$output" | grep -Eq 'smart-log .*/nvme0 -o json'
run /bin/bash "$provider" run storage.health nvme0n1 -
[ "$status" -ne 0 ]
[[ "$output" == *'invalid NVMe controller identity'* ]]
}
@test "the mstflint provider returns Mellanox devices only, with the tool output" {
provider="$(require_repo_file "$HW_FILES/provider-mstflint")"
printf '#!/bin/sh\nprintf "%%s\\n" "firmware query"\n' >"$bin/mstflint"
chmod 0755 "$bin/mstflint"
pci="$root/sys/bus/pci/devices"
mkdir -p "$pci/0000:03:00.0" "$pci/0000:04:00.0"
printf '0x15b3\n' >"$pci/0000:03:00.0/vendor"
printf '0x8086\n' >"$pci/0000:04:00.0/vendor"
export XCAT_GENESIS_SYS_PCI_DEVICES="$pci"
run /bin/bash "$provider" probe
[ "$status" -eq 0 ]
[ "$(jq -c .devices <<<"$output")" = '["0000:03:00.0"]' ]
run /bin/bash "$provider" run network.firmware.inventory 0000:03:00.0 -
[ "$status" -eq 0 ]
[ "$(jq -r .raw <<<"$output")" = 'firmware query' ]
run /bin/bash "$provider" run network.firmware.inventory 0000:04:00.0 -
[ "$status" -ne 0 ]
[[ "$output" == *'device is not NVIDIA or Mellanox: 0000:04:00.0'* ]]
}
@test "the iprutils provider returns Power RAID hosts, the tool output, and rejects changes" {
provider="$(require_repo_file "$HW_FILES/provider-iprutils")"
cat >"$bin/iprconfig" <<'SH'
#!/bin/sh
case "$*" in
'-c show-config') printf '%s\n' 'Power RAID configuration' ;;
'-c show-arrays') printf '%s\n' 'Power RAID arrays healthy' ;;
'-c show-ucode-levels') printf '%s\n' 'Power RAID firmware levels' ;;
*) exit 2 ;;
esac
SH
chmod 0755 "$bin/iprconfig"
hosts="$root/sys/class/scsi_host"
mkdir -p "$hosts/host0" "$hosts/host1"
printf 'ipr\n' >"$hosts/host0/proc_name"
printf 'megaraid_sas\n' >"$hosts/host1/proc_name"
export XCAT_GENESIS_SYS_SCSI_HOSTS="$hosts"
run /bin/bash "$provider" probe
[ "$status" -eq 0 ]
[ "$(jq -c .devices <<<"$output")" = '["host0"]' ]
for pair in 'storage.inventory:Power RAID configuration' \
'storage.health:Power RAID arrays healthy' \
'storage.firmware.inventory:Power RAID firmware levels'; do
run /bin/bash "$provider" run "${pair%%:*}" host0 -
[ "$status" -eq 0 ]
[ "$(jq -r .raw <<<"$output")" = "${pair#*:}" ]
done
run /bin/bash "$provider" run storage.array.create host0 -
[ "$status" -ne 0 ]
[[ "$output" == *'unsupported capability: storage.array.create'* ]]
}