Files
spinifex/tests/e2e/lib/multinode-helpers.sh
T
2026-01-27 10:59:58 +11:00

328 lines
9.6 KiB
Bash
Executable File

#!/bin/bash
# Multi-node E2E test helper functions
# Provides utilities for managing simulated IPs, starting/stopping node services,
# and verifying NATS cluster health.
# Suppress urllib3 InsecureRequestWarning from AWS CLI (we use --no-verify-ssl)
export PYTHONWARNINGS="ignore::urllib3.exceptions.InsecureRequestWarning"
# Helper to filter AWS CLI output (removes Python warnings that break jq)
aws_json() {
"$@" 2>&1 | grep -v "^urllib3\|^InsecureRequestWarning\|certificate verification"
}
# Network configuration
SIMULATED_NETWORK="10.11.12"
NODE1_IP="${SIMULATED_NETWORK}.1"
NODE2_IP="${SIMULATED_NETWORK}.2"
NODE3_IP="${SIMULATED_NETWORK}.3"
# Port configuration
NATS_CLIENT_PORT=4222
NATS_CLUSTER_PORT=4248
NATS_MONITOR_PORT=8222
PREDASTORE_PORT=8443
AWSGW_PORT=9999
CLUSTER_PORT=4432
# Add simulated IPs to loopback interface
# Requires NET_ADMIN capability
add_simulated_ips() {
echo "Adding simulated IPs to loopback interface..."
for i in 1 2 3; do
local ip="${SIMULATED_NETWORK}.$i"
if ! ip addr show lo | grep -q "$ip"; then
ip addr add "${ip}/24" dev lo
echo " Added $ip to lo"
else
echo " $ip already exists on lo"
fi
done
# Verify IPs were added
echo "Verifying simulated IPs..."
for i in 1 2 3; do
local ip="${SIMULATED_NETWORK}.$i"
if ip addr show lo | grep -q "$ip"; then
echo " $ip is configured"
else
echo " ERROR: Failed to add $ip"
return 1
fi
done
echo "Simulated IPs configured successfully"
}
# Remove simulated IPs from loopback interface
remove_simulated_ips() {
echo "Removing simulated IPs from loopback interface..."
for i in 1 2 3; do
local ip="${SIMULATED_NETWORK}.$i"
if ip addr show lo | grep -q "$ip"; then
ip addr del "${ip}/24" dev lo 2>/dev/null || true
echo " Removed $ip from lo"
fi
done
echo "Simulated IPs removed"
}
# Start services for a specific node
# Usage: start_node_services <node_num> <data_dir>
# Example: start_node_services 1 ~/node1
start_node_services() {
local node_num="$1"
local data_dir="$2"
local node_ip="${SIMULATED_NETWORK}.$node_num"
echo "Starting services for node$node_num at $node_ip..."
# Start all services - each node's config binds to its specific IP
HIVE_SKIP_BUILD=true ./scripts/start-dev.sh "$data_dir"
echo "Node$node_num services started"
}
# Stop services for a specific node
# Usage: stop_node_services <node_num> <data_dir>
stop_node_services() {
local node_num="$1"
local data_dir="$2"
echo "Stopping services for node$node_num..."
# Stop using PID files in the node's log directory
./scripts/stop-dev.sh "$data_dir"
echo "Node$node_num services stopped"
}
# Stop all node services
stop_all_nodes() {
echo "Stopping all node services..."
for i in 1 2 3; do
local data_dir="$HOME/node$i"
if [ -d "$data_dir/logs" ]; then
stop_node_services "$i" "$data_dir" || true
fi
done
echo "All node services stopped"
}
# Verify NATS cluster health
# Checks that the cluster has expected number of members
# Usage: verify_nats_cluster [expected_members]
verify_nats_cluster() {
local expected_members="${1:-3}"
echo "Verifying NATS cluster health (expecting $expected_members members)..."
# Check cluster info via monitoring endpoint on node1
local cluster_info
cluster_info=$(curl -s "http://${NODE1_IP}:${NATS_MONITOR_PORT}/routez" 2>/dev/null) || {
echo " ERROR: Cannot reach NATS monitoring endpoint"
return 1
}
# Count unique remote servers (NATS creates multiple connections per peer)
local num_routes
num_routes=$(echo "$cluster_info" | jq -r '.num_routes // 0')
# Count unique peer names
local unique_peers
unique_peers=$(echo "$cluster_info" | jq -r '[.routes[].remote_name] | unique | length')
local expected_peers=$((expected_members - 1))
echo " NATS cluster routes: $num_routes (unique peers: $unique_peers, expected peers: $expected_peers)"
if [ "$unique_peers" -ge "$expected_peers" ]; then
echo " NATS cluster is healthy"
return 0
else
echo " WARNING: NATS cluster may not be fully formed"
echo " Cluster info: $cluster_info"
return 1
fi
}
# Wait for a specific instance state
# Usage: wait_for_instance_state <instance_id> <target_state> [max_attempts]
wait_for_instance_state() {
local instance_id="$1"
local target_state="$2"
local max_attempts="${3:-10}"
local attempt=0
echo "Waiting for instance $instance_id to reach state: $target_state..."
while [ $attempt -lt $max_attempts ]; do
local state
state=$(aws_json aws --endpoint-url https://${NODE1_IP}:${AWSGW_PORT} --no-verify-ssl ec2 describe-instances \
--instance-ids "$instance_id" \
--query 'Reservations[0].Instances[0].State.Name' \
--output text) || {
echo " Attempt $((attempt + 1))/$max_attempts - Gateway request failed, retrying..."
sleep 2
attempt=$((attempt + 1))
continue
}
echo " Instance state: $state"
if [ "$state" == "$target_state" ]; then
echo " Instance reached target state: $target_state"
return 0
fi
if [ "$state" == "terminated" ] && [ "$target_state" != "terminated" ]; then
echo " ERROR: Instance terminated unexpectedly"
return 1
fi
sleep 2
attempt=$((attempt + 1))
done
echo " ERROR: Instance did not reach $target_state within $max_attempts attempts"
return 1
}
# Wait for gateway to be ready
# Usage: wait_for_gateway [host] [max_attempts]
wait_for_gateway() {
local host="${1:-localhost}"
local max_attempts="${2:-30}"
local attempt=0
echo "Waiting for AWS Gateway at $host:${AWSGW_PORT}..."
while [ $attempt -lt $max_attempts ]; do
if curl -k -s "https://${host}:${AWSGW_PORT}" > /dev/null 2>&1; then
echo " Gateway is ready"
return 0
fi
echo " Waiting for gateway... ($((attempt + 1))/$max_attempts)"
sleep 2
attempt=$((attempt + 1))
done
echo " ERROR: Gateway failed to start"
return 1
}
# Check instance distribution across nodes
# Returns the instance counts per node
check_instance_distribution() {
echo "Checking instance distribution across nodes..."
local total=0
for i in 1 2 3; do
local data_dir="$HOME/node$i"
local instances_file="$data_dir/hive/instances.json"
if [ -f "$instances_file" ]; then
local count
count=$(jq 'length' "$instances_file" 2>/dev/null || echo "0")
echo " Node$i: $count instances"
total=$((total + count))
else
echo " Node$i: 0 instances (no instances file)"
fi
done
echo " Total instances: $total"
}
# Dump logs from all nodes (for debugging failures)
dump_all_node_logs() {
echo ""
echo "=========================================="
echo "DUMPING LOGS FROM ALL NODES"
echo "=========================================="
for i in 1 2 3; do
local data_dir="$HOME/node$i"
local logs_dir="$data_dir/logs"
if [ -d "$logs_dir" ]; then
echo ""
echo "=== Node$i Logs ==="
for log in nats predastore viperblock hive awsgw; do
if [ -f "$logs_dir/$log.log" ]; then
echo ""
echo "--- $log.log (last 50 lines) ---"
tail -50 "$logs_dir/$log.log" 2>/dev/null || echo "(empty or not accessible)"
fi
done
fi
done
echo ""
echo "=========================================="
echo "END OF LOG DUMP"
echo "=========================================="
}
# Initialize leader node (node1)
# Usage: init_leader_node
init_leader_node() {
echo "Initializing leader node (node1)..."
# Remove old node directory
rm -rf "$HOME/node1/"
# Node1 routes to itself to enable clustering mode (NATS ignores self-routes)
# Other nodes will discover each other via gossip when they connect
./bin/hive admin init \
--node node1 \
--bind "${NODE1_IP}" \
--cluster-bind "${NODE1_IP}" \
--cluster-routes "${NODE1_IP}:${NATS_CLUSTER_PORT}" \
--port ${CLUSTER_PORT} \
--region ap-southeast-2 \
--az ap-southeast-2a \
--hive-dir "$HOME/node1/" \
--config-dir "$HOME/node1/config/"
echo "Leader node initialized"
}
# Join a follower node to the cluster
# Usage: join_follower_node <node_num>
join_follower_node() {
local node_num="$1"
local node_ip="${SIMULATED_NETWORK}.$node_num"
local data_dir="$HOME/node$node_num"
echo "Joining node$node_num ($node_ip) to cluster..."
# Remove old node directory
rm -rf "$data_dir/"
# Route to node1 (seed node) - other nodes discovered via NATS gossip
./bin/hive admin join \
--node "node$node_num" \
--bind "$node_ip" \
--cluster-bind "$node_ip" \
--cluster-routes "${NODE1_IP}:${NATS_CLUSTER_PORT}" \
--host "${NODE1_IP}:${CLUSTER_PORT}" \
--data-dir "$data_dir/" \
--config-dir "$data_dir/config/" \
--region ap-southeast-2 \
--az "ap-southeast-2a"
# Point predastore to node1 (only node1 runs predastore until multi-node predastore is implemented)
sed -i "s|host = \"${node_ip}:8443\"|host = \"${NODE1_IP}:8443\"|" "$data_dir/config/hive.toml"
echo "Node$node_num joined cluster"
}