Files
EasyTier/script/test-cli-multi-instance.sh
T
fanyang d96eaa668b feat(cli): show direct peers using temporary credentials (#2663)
Add credential peers with table and JSON output while preserving credential list.
Match authenticated remote credential keys to stored fingerprints and virtual
route addresses. Keep instance results isolated and exclude unproven relay users.
Enable SHA-256 for CLI-only builds and add regression and multi-instance tests.
Accept omitted protobuf default lists in the existing multi-instance test.

Refs #2529
2026-10-10 12:33:47 +08:00

300 lines
7.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
CORE_BIN=${CORE_BIN:-"$REPO_ROOT/target/debug/easytier-core"}
CLI_BIN=${CLI_BIN:-"$REPO_ROOT/target/debug/easytier-cli"}
TMPDIR_PATH=""
CORE_PID=""
PYTHON_BIN=${PYTHON_BIN:-python3}
print_section() {
printf '\n==> %s\n' "$1"
}
print_output() {
local title="$1"
local content="$2"
printf -- '---- %s ----\n' "$title"
printf '%s\n' "$content"
printf -- '---- end %s ----\n' "$title"
}
build_binaries() {
print_section "Building easytier-core and easytier-cli"
cargo build -p easytier --bin easytier-core --bin easytier-cli
}
ensure_binaries() {
if [[ "${SKIP_BUILD:-0}" != "1" ]] || [[ ! -x "$CORE_BIN" ]] || [[ ! -x "$CLI_BIN" ]]; then
build_binaries
fi
}
make_tmpdir() {
"$PYTHON_BIN" - <<'PY'
import tempfile
print(tempfile.mkdtemp(prefix="easytier-cli-e2e-"))
PY
}
cleanup_tmpdir() {
TMPDIR_TO_DELETE="$1" "$PYTHON_BIN" - <<'PY'
import os
import shutil
shutil.rmtree(os.environ["TMPDIR_TO_DELETE"], ignore_errors=True)
PY
}
alloc_port() {
"$PYTHON_BIN" - <<'PY'
import socket
sock = socket.socket()
sock.bind(("127.0.0.1", 0))
print(sock.getsockname()[1])
sock.close()
PY
}
wait_for_cli() {
local rpc_port="$1"
local attempts=0
while (( attempts < 50 )); do
if "$CLI_BIN" -p "127.0.0.1:${rpc_port}" -o json node >/dev/null 2>&1; then
return 0
fi
attempts=$((attempts + 1))
sleep 0.2
done
return 1
}
run_cmd() {
local __var_name="$1"
local title="$2"
shift 2
print_section "$title"
printf '+'
for arg in "$@"; do
printf ' %q' "$arg"
done
printf '\n'
local output
if ! output=$("$@" 2>&1); then
print_output "$title output" "$output"
return 1
fi
print_output "$title output" "$output"
printf -v "$__var_name" '%s' "$output"
}
assert_text_output() {
local text_output="$1"
grep -F '== e2e-inst-a (' <<<"$text_output" >/dev/null
grep -F '== e2e-inst-b (' <<<"$text_output" >/dev/null
}
assert_multi_instance_json() {
local json_payload="$1"
JSON_PAYLOAD="$json_payload" "$PYTHON_BIN" - <<'PY'
import json
import os
data = json.loads(os.environ["JSON_PAYLOAD"])
assert isinstance(data, list), data
assert len(data) == 2, data
names = {item["instance_name"] for item in data}
assert names == {"e2e-inst-a", "e2e-inst-b"}, names
for item in data:
assert item["instance_id"], item
assert isinstance(item["result"], dict), item
PY
}
assert_single_instance_json() {
local json_payload="$1"
JSON_PAYLOAD="$json_payload" "$PYTHON_BIN" - <<'PY'
import json
import os
data = json.loads(os.environ["JSON_PAYLOAD"])
assert isinstance(data, dict), data
assert data["config"].find('instance_name = "e2e-inst-a"') >= 0, data["config"]
PY
}
assert_whitelist_fanout() {
local json_payload="$1"
JSON_PAYLOAD="$json_payload" "$PYTHON_BIN" - <<'PY'
import json
import os
data = json.loads(os.environ["JSON_PAYLOAD"])
assert len(data) == 2, data
for item in data:
assert item["result"]["tcp_ports"] == ["80", "443"], item
assert item["result"].get("udp_ports", []) == [], item
PY
}
assert_single_instance_write() {
local json_payload="$1"
JSON_PAYLOAD="$json_payload" "$PYTHON_BIN" - <<'PY'
import json
import os
data = {item["instance_name"]: item["result"] for item in json.loads(os.environ["JSON_PAYLOAD"])}
assert data["e2e-inst-a"]["tcp_ports"] == ["80", "443"], data
assert data["e2e-inst-b"].get("tcp_ports", []) == [], data
PY
}
main() {
command -v "$PYTHON_BIN" >/dev/null 2>&1 || {
echo "python interpreter not found: $PYTHON_BIN" >&2
exit 1
}
ensure_binaries
TMPDIR_PATH=$(make_tmpdir)
print_section "Created temporary test directory"
printf '%s\n' "$TMPDIR_PATH"
local rpc_port
rpc_port=$(alloc_port)
print_section "Allocated RPC port"
printf '%s\n' "$rpc_port"
cleanup() {
if [[ -n "$CORE_PID" ]] && kill -0 "$CORE_PID" >/dev/null 2>&1; then
kill "$CORE_PID" >/dev/null 2>&1 || true
wait "$CORE_PID" >/dev/null 2>&1 || true
fi
if [[ -n "$TMPDIR_PATH" ]]; then
cleanup_tmpdir "$TMPDIR_PATH"
fi
}
trap cleanup EXIT
cat >"$TMPDIR_PATH/inst-a.toml" <<'EOF'
instance_name = "e2e-inst-a"
listeners = []
[network_identity]
network_name = "e2e-net-a"
network_secret = ""
[flags]
no_tun = true
enable_ipv6 = false
EOF
cat >"$TMPDIR_PATH/inst-b.toml" <<'EOF'
instance_name = "e2e-inst-b"
listeners = []
[network_identity]
network_name = "e2e-net-b"
network_secret = ""
[flags]
no_tun = true
enable_ipv6 = false
EOF
"$CORE_BIN" --config-dir "$TMPDIR_PATH" --rpc-portal "127.0.0.1:${rpc_port}" \
>"$TMPDIR_PATH/core.log" 2>&1 &
CORE_PID=$!
print_section "Started easytier-core"
printf 'pid=%s\n' "$CORE_PID"
wait_for_cli "$rpc_port"
print_output "easytier-core startup log" "$(cat "$TMPDIR_PATH/core.log")"
local text_output
run_cmd text_output \
"Case 1: node fanout in table mode" \
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" node
assert_text_output "$text_output"
local json_output
run_cmd json_output \
"Case 2: node fanout in JSON mode" \
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" -o json node
assert_multi_instance_json "$json_output"
local single_output
run_cmd single_output \
"Case 3: explicit instance selector stays single-instance" \
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" --instance-name e2e-inst-a -o json node
assert_single_instance_json "$single_output"
local set_whitelist_output
run_cmd set_whitelist_output \
"Case 4: whitelist set-tcp fans out to all instances" \
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" whitelist set-tcp 80,443
local whitelist_output
run_cmd whitelist_output \
"Case 5: whitelist show confirms fanout write" \
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" -o json whitelist show
assert_whitelist_fanout "$whitelist_output"
local clear_whitelist_output
run_cmd clear_whitelist_output \
"Case 6: explicit selector write only touches one instance" \
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" --instance-name e2e-inst-b whitelist clear-tcp
local cleared_output
run_cmd cleared_output \
"Case 7: whitelist show confirms single-instance write isolation" \
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" -o json whitelist show
assert_single_instance_write "$cleared_output"
local credential_table
run_cmd credential_table \
"Case 8: credential peers table fans out to all instances" \
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" credential peers
assert_text_output "$credential_table"
local credential_json
run_cmd credential_json \
"Case 9: credential peers JSON preserves instance wrappers" \
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" -o json credential peers
assert_multi_instance_json "$credential_json"
JSON_PAYLOAD="$credential_json" "$PYTHON_BIN" - <<'PY'
import json
import os
data = json.loads(os.environ["JSON_PAYLOAD"])
assert all(item["result"] == {"credentials": []} for item in data), data
PY
local credential_single
run_cmd credential_single \
"Case 10: credential peers explicit selector returns one instance" \
"$CLI_BIN" -p "127.0.0.1:${rpc_port}" --instance-name e2e-inst-a -o json credential peers
JSON_PAYLOAD="$credential_single" "$PYTHON_BIN" - <<'PY'
import json
import os
assert json.loads(os.environ["JSON_PAYLOAD"]) == {"credentials": []}
PY
print_section "Result"
echo "CLI multi-instance E2E passed"
}
main "$@"