214 lines
9.2 KiB
Bash
214 lines
9.2 KiB
Bash
#!/usr/bin/env bash
|
|
set -u -o pipefail
|
|
|
|
TEST_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
source "${TEST_ROOT}/tests/testlib.sh"
|
|
source "${TEST_ROOT}/inc/deploy-pulse-lxc.sh"
|
|
|
|
assert_success "stable Pulse release" TAPM_PULSE_VALID_RELEASE v6.1.1
|
|
assert_success "prerelease Pulse release" TAPM_PULSE_VALID_RELEASE v6.2.0-rc.1
|
|
assert_failure "Pulse release requires v prefix" TAPM_PULSE_VALID_RELEASE 6.1.1
|
|
assert_failure "Pulse release rejects URL content" \
|
|
TAPM_PULSE_VALID_RELEASE 'v6.1.1/../../latest'
|
|
|
|
assert_equal amd64 "$(TAPM_PULSE_ARCH x86_64)" "x86 architecture mapping"
|
|
assert_equal arm64 "$(TAPM_PULSE_ARCH aarch64)" "ARM architecture mapping"
|
|
assert_failure "unsupported Pulse architecture" TAPM_PULSE_ARCH riscv64
|
|
|
|
test_bridge_selection_assignment() {
|
|
local bridge=''
|
|
|
|
TAPM_PULSE_SET_BRIDGE_FROM_SELECTION bridge 'bridge:vmbr0' || return 1
|
|
assert_equal vmbr0 "$bridge" "selected Pulse bridge assigned to caller"
|
|
}
|
|
|
|
assert_success "Pulse bridge selection assignment" test_bridge_selection_assignment
|
|
assert_failure "empty Pulse bridge selection rejected" \
|
|
TAPM_PULSE_SET_BRIDGE_FROM_SELECTION selected_bridge 'bridge:'
|
|
|
|
bootstrap_token='0123456789abcdef0123456789abcdef0123456789abcdef'
|
|
bootstrap_output="║ Token: ${bootstrap_token} ║"
|
|
assert_equal "$bootstrap_token" \
|
|
"$(TAPM_PULSE_BOOTSTRAP_TOKEN_FROM_OUTPUT "$bootstrap_output")" \
|
|
"Pulse bootstrap token parsed"
|
|
assert_failure "malformed Pulse bootstrap output rejected" \
|
|
TAPM_PULSE_BOOTSTRAP_TOKEN_FROM_OUTPUT 'Token: not-a-token'
|
|
|
|
agent_token='0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
|
|
assert_equal "$agent_token" \
|
|
"$(TAPM_PULSE_TOKEN_FROM_RESPONSE "{\"token\":\"${agent_token}\"}")" \
|
|
"Pulse agent token parsed"
|
|
assert_failure "missing Pulse agent token rejected" \
|
|
TAPM_PULSE_TOKEN_FROM_RESPONSE '{"command":"install"}'
|
|
assert_failure "Pulse agent token containing whitespace rejected" \
|
|
TAPM_PULSE_TOKEN_FROM_RESPONSE '{"token":"invalid token"}'
|
|
assert_failure "non-hex Pulse agent token rejected" \
|
|
TAPM_PULSE_TOKEN_FROM_RESPONSE '{"token":"not-a-token"}'
|
|
assert_success "registered Pulse agent response accepted" \
|
|
TAPM_PULSE_AGENT_REGISTERED_FROM_RESPONSE \
|
|
'{"agent":{"id":"agent-123","hostname":"pve1"}}'
|
|
assert_failure "missing Pulse agent ID rejected" \
|
|
TAPM_PULSE_AGENT_REGISTERED_FROM_RESPONSE '{"agent":{"hostname":"pve1"}}'
|
|
assert_equal '{"type":"pve","enableCommands":true}' \
|
|
"$(TAPM_PULSE_AGENT_TOKEN_REQUEST_JSON)" \
|
|
"Pulse PVE unified-agent enrollment requested with commands enabled"
|
|
|
|
setup_token='0123456789abcdef0123456789abcdef'
|
|
setup_host='https://pve1.example.test:8006'
|
|
setup_old_url='https://pulse.example.test/api/setup-script?backup_perms=true'
|
|
setup_response="$(
|
|
SETUP_TOKEN="$setup_token" SETUP_HOST="$setup_host" SETUP_OLD_URL="$setup_old_url" \
|
|
python3 -c '
|
|
import json, os, sys, time
|
|
old_url = os.environ["SETUP_OLD_URL"]
|
|
token = os.environ["SETUP_TOKEN"]
|
|
json.dump({
|
|
"type": "pve",
|
|
"host": os.environ["SETUP_HOST"],
|
|
"url": old_url,
|
|
"downloadURL": old_url + "&setup_token=" + token,
|
|
"scriptFileName": "pulse-setup-pve.sh",
|
|
"command": old_url + " PULSE_SETUP_TOKEN=" + token + " if",
|
|
"commandWithEnv": old_url + " PULSE_SETUP_TOKEN=" + token + " if",
|
|
"commandWithoutEnv": old_url + " if",
|
|
"expires": int(time.time()) + 300,
|
|
"setupToken": token,
|
|
"tokenHint": token[:3] + "..." + token[-3:],
|
|
}, sys.stdout)
|
|
'
|
|
)"
|
|
normalized_setup="$(
|
|
TAPM_PULSE_NORMALIZE_V611_SETUP_ARTIFACT \
|
|
"$setup_response" 'http://10.10.2.30:7655'
|
|
)"
|
|
assert_equal "$setup_token" \
|
|
"$(SETUP_RESPONSE="$normalized_setup" python3 -c 'import json, os; print(json.loads(os.environ["SETUP_RESPONSE"])["setupToken"])')" \
|
|
"Pulse v6.1.1 compatibility preserves server-issued setup token"
|
|
assert_equal \
|
|
'http://10.10.2.30:7655/api/setup-script?host=https%3A%2F%2Fpve1.example.test%3A8006&pulse_url=http%3A%2F%2F10.10.2.30%3A7655&type=pve' \
|
|
"$(SETUP_RESPONSE="$normalized_setup" python3 -c 'import json, os; print(json.loads(os.environ["SETUP_RESPONSE"])["url"])')" \
|
|
"Pulse v6.1.1 compatibility normalizes setup artifact URL"
|
|
assert_failure "Pulse v6.1.1 compatibility rejects missing setup token" \
|
|
TAPM_PULSE_NORMALIZE_V611_SETUP_ARTIFACT \
|
|
'{"type":"pve","host":"https://pve1:8006"}' 'http://10.10.2.30:7655'
|
|
|
|
test_pve_token_output_assignment() {
|
|
local token_output=''
|
|
local token_status=99
|
|
|
|
pveum() {
|
|
printf '%s\n' \
|
|
'{"full-tokenid":"pulse-monitor@pve!pulse-test","value":"secret-value"}'
|
|
}
|
|
TAPM_PULSE_CREATE_PVE_AUTO_REGISTER_TOKEN \
|
|
'pulse-test' token_output token_status || return 1
|
|
unset -f pveum
|
|
|
|
assert_equal \
|
|
'{"full-tokenid":"pulse-monitor@pve!pulse-test","value":"secret-value"}' \
|
|
"$token_output" \
|
|
"Pulse receives pveum token output"
|
|
assert_equal 0 "$token_status" "Pulse receives pveum token status"
|
|
}
|
|
|
|
assert_success "Pulse v6.1.1 pveum output-shadowing workaround" \
|
|
test_pve_token_output_assignment
|
|
|
|
nodes_json='[
|
|
{"node":"pve3","status":"offline"},
|
|
{"node":"pve2","status":"online"},
|
|
{"node":"pve1","status":"online"}
|
|
]'
|
|
assert_equal $'pve1\npve2' \
|
|
"$(TAPM_PULSE_ONLINE_NODES_FROM_JSON "$nodes_json")" \
|
|
"online Pulse nodes selected and sorted"
|
|
assert_equal 'pve3' \
|
|
"$(TAPM_PULSE_OFFLINE_NODES_FROM_JSON "$nodes_json")" \
|
|
"offline Pulse nodes reported"
|
|
|
|
assert_success "valid Pulse CTID" TAPM_PULSE_VALID_CTID 210
|
|
assert_failure "invalid Pulse CTID" TAPM_PULSE_VALID_CTID 99
|
|
ctid_resources='[
|
|
{"type":"qemu","vmid":200},
|
|
{"type":"lxc","vmid":"201"},
|
|
{"type":"storage","storage":"local"}
|
|
]'
|
|
assert_equal 202 \
|
|
"$(TAPM_PULSE_FIRST_AVAILABLE_CTID_FROM_RESOURCES "$ctid_resources" 200)" \
|
|
"first available Pulse CTID starts at 200 and skips occupied IDs"
|
|
assert_equal 200 \
|
|
"$(TAPM_PULSE_FIRST_AVAILABLE_CTID_FROM_RESOURCES '[]' 200)" \
|
|
"Pulse CTID 200 selected when available"
|
|
assert_failure "invalid Pulse CTID resource data rejected" \
|
|
TAPM_PULSE_FIRST_AVAILABLE_CTID_FROM_RESOURCES '{}' 200
|
|
assert_success "valid Pulse hostname" TAPM_PULSE_VALID_HOSTNAME pulse-monitor
|
|
assert_failure "invalid Pulse hostname" TAPM_PULSE_VALID_HOSTNAME 'pulse monitor'
|
|
assert_success "valid positive integer" TAPM_PULSE_VALID_POSITIVE_INTEGER 1024
|
|
assert_failure "zero is not positive" TAPM_PULSE_VALID_POSITIVE_INTEGER 0
|
|
assert_success "zero is a valid nonnegative integer" \
|
|
TAPM_PULSE_VALID_NONNEGATIVE_INTEGER 0
|
|
assert_failure "negative integer rejected" TAPM_PULSE_VALID_NONNEGATIVE_INTEGER -1
|
|
assert_success "valid Pulse port" TAPM_PULSE_VALID_PORT 7655
|
|
assert_failure "Pulse port too high" TAPM_PULSE_VALID_PORT 65536
|
|
assert_success "valid Pulse IPv4 CIDR" TAPM_PULSE_VALID_IPV4_CIDR 10.10.1.50/24
|
|
assert_failure "invalid Pulse IPv4 CIDR" TAPM_PULSE_VALID_IPV4_CIDR 10.10.1.500/24
|
|
assert_success "valid Pulse gateway" TAPM_PULSE_VALID_IPV4 10.10.0.1
|
|
assert_failure "gateway CIDR rejected" TAPM_PULSE_VALID_IPV4 10.10.0.1/16
|
|
assert_success "blank optional Pulse address accepted" \
|
|
TAPM_PULSE_VALID_OPTIONAL_IPV4_CIDR ''
|
|
assert_success "valid Pulse VLAN" TAPM_PULSE_VALID_OPTIONAL_VLAN 4094
|
|
assert_failure "Pulse VLAN zero rejected" TAPM_PULSE_VALID_OPTIONAL_VLAN 0
|
|
assert_success "blank Pulse VLAN accepted" TAPM_PULSE_VALID_OPTIONAL_VLAN ''
|
|
|
|
assert_success "12-character Pulse admin password accepted" \
|
|
TAPM_PULSE_VALID_ADMIN_PASSWORD '123456789012'
|
|
assert_success "72-byte Pulse admin password accepted" \
|
|
TAPM_PULSE_VALID_ADMIN_PASSWORD \
|
|
'123456789012345678901234567890123456789012345678901234567890123456789012'
|
|
assert_failure "short Pulse admin password rejected" \
|
|
TAPM_PULSE_VALID_ADMIN_PASSWORD '12345678901'
|
|
assert_failure "multibyte Pulse admin password still requires 12 characters" \
|
|
TAPM_PULSE_VALID_ADMIN_PASSWORD 'éééééé'
|
|
assert_failure "Pulse admin password above bcrypt limit rejected" \
|
|
TAPM_PULSE_VALID_ADMIN_PASSWORD \
|
|
'1234567890123456789012345678901234567890123456789012345678901234567890123'
|
|
|
|
resources='[
|
|
{"type":"lxc","name":"pulse-a","tags":"tapm;pulse"},
|
|
{"type":"qemu","name":"unrelated"}
|
|
]'
|
|
assert_success "tagged Pulse LXC detected" TAPM_PULSE_RESOURCE_INSTALLED "$resources"
|
|
assert_success "legacy Pulse hostname detected" \
|
|
TAPM_PULSE_RESOURCE_INSTALLED '[{"type":"lxc","name":"Pulse"}]'
|
|
assert_success "untagged default Pulse hostname detected" \
|
|
TAPM_PULSE_RESOURCE_INSTALLED '[{"type":"lxc","name":"Pulse-Monitor"}]'
|
|
assert_failure "unrelated resource not detected" \
|
|
TAPM_PULSE_RESOURCE_INSTALLED '[{"type":"qemu","name":"pulse"}]'
|
|
|
|
test_pulse_config_detection() {
|
|
local fixture_root status
|
|
|
|
fixture_root="$(mktemp -d /tmp/tapm-pulse-configs.XXXXXX)" || return 1
|
|
mkdir -p "${fixture_root}/pve1/lxc" || return 1
|
|
printf '%s\n' \
|
|
'arch: amd64' \
|
|
'hostname: Pulse-Monitor' \
|
|
'memory: 2048' >"${fixture_root}/pve1/lxc/200.conf"
|
|
TAPM_PULSE_RESOURCE_INSTALLED_FROM_CONFIGS "$fixture_root"
|
|
status=$?
|
|
rm -rf -- "$fixture_root"
|
|
return "$status"
|
|
}
|
|
|
|
assert_success "Pulse LXC detected from shared Proxmox configuration" \
|
|
test_pulse_config_detection
|
|
assert_failure "missing Pulse configuration directory is not installed" \
|
|
TAPM_PULSE_RESOURCE_INSTALLED_FROM_CONFIGS /does/not/exist
|
|
|
|
ha_status=$'quorum OK\nmaster pve1 (active, Sat Jul 25 12:00:00 2026)\nlrm pve1 (active, Sat Jul 25 12:00:00 2026)'
|
|
assert_success "active Proxmox HA detected" TAPM_PULSE_HA_STATUS_ENABLED "$ha_status"
|
|
assert_failure "inactive Proxmox HA rejected" \
|
|
TAPM_PULSE_HA_STATUS_ENABLED $'quorum OK\nmaster pve1 (idle)'
|
|
|
|
finish_tests
|