mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-10-22 07:38:08 +00:00
c6fc094c93
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>
265 lines
9.5 KiB
Bash
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'* ]]
|
|
}
|