diff --git a/src/controller/ha.c b/src/controller/ha.c index 35deaefb..9bd74d4d 100644 --- a/src/controller/ha.c +++ b/src/controller/ha.c @@ -550,6 +550,19 @@ static int raft_cb_persist(void *ctx, uint64_t term, int voted_for) return 0; } +/* raft_cb_trace: emit raft state-machine events at DEBUG3 level. + * Active when controller is started with -vvv; suppressed otherwise. */ +static void raft_cb_trace(void *ctx, const char *event, const char *detail) +{ + ha_state_t *ha = (ha_state_t *)ctx; + tlog(TLOG_DEBUG3, "RAFT[%s] term=%" PRIu64 " event=%s%s%s", + ha->my_priority == 0 ? "p0" : + ha->my_priority == 1 ? "p1" : "p2", + atomic_load_explicit(&ha->term, memory_order_relaxed), + event, + detail ? " " : "", detail ? detail : ""); +} + static const raft_callbacks_t s_real_cbs = { .on_send_vote_request = raft_cb_send_vote_request, .on_vote_eligible = raft_cb_vote_eligible, @@ -559,7 +572,7 @@ static const raft_callbacks_t s_real_cbs = { .on_schedule_timeout = raft_cb_schedule_timeout, .on_cancel_timeout = raft_cb_cancel_timeout, .on_start_election_after_delay = raft_cb_start_election_after_delay, - .on_trace = NULL, + .on_trace = raft_cb_trace, }; /* ── PRNG seeding ───────────────────────────────────────────── */ @@ -2058,6 +2071,8 @@ static int repl_ack_decode_and_dispatch(endpoint_t *ep, vote_ep_ctx_t *vctx) if (opcode == OPCode_replCatchUpDone) { ha_repl_ack_received(ha, c); + } else if (opcode == OPCode_nop || opcode == OPCode_reply) { + /* Heartbeat NOP reply or generic reply — silently drop */ } else { WARN("repl_ack_decode_and_dispatch: unexpected opcode %d on promoted ep", opcode); diff --git a/src/tools/deploy-ha.sh b/src/tools/deploy-ha.sh new file mode 100755 index 00000000..8e5b68d2 --- /dev/null +++ b/src/tools/deploy-ha.sh @@ -0,0 +1,78 @@ +#!/bin/bash +set -e + +BINARY=$1 +DATADIR=$2 +TOP=$3 +PEERS=$4 + +if [ -z "$BINARY" ] || [ -z "$DATADIR" ] || [ -z "$TOP" ] || [ -z "$PEERS" ]; then + echo "Usage: $0 " + echo " peers: comma-separated host:port list (e.g. host1:port1,host2:port2)" + exit 1 +fi + +BINARY=$(realpath "$BINARY") + +IFS=',' read -ra PEER_ARRAY <<< "$PEERS" +N=${#PEER_ARRAY[@]} + +# Create directory structure +mkdir -p "$TOP/certs" +mkdir -p "$TOP/logs" +mkdir -p "$TOP/scripts" +for i in $(seq 1 $N); do + mkdir -p "$TOP/ctrl-$i/conf" + mkdir -p "$TOP/ctrl-$i/data" +done + +# Copy certificates +cp "$DATADIR/xce.crt" "$TOP/certs/" +cp "$DATADIR/xce.key" "$TOP/certs/" + +# Tag and copy cluster.bin +TAG=$(sha256sum "$DATADIR/cluster.bin" | cut -c1-12) +cp "$DATADIR/cluster.bin" "$TOP/ctrl-1/conf/cluster.bin.$TAG" +echo "cluster.bin.$TAG" > "$TOP/ctrl-1/conf/current" + +# Build peers string: ctrl-1=host1:port1,ctrl-2=host2:port2,... +PEERS_STR="" +for i in "${!PEER_ARRAY[@]}"; do + n=$((i+1)) + [ -n "$PEERS_STR" ] && PEERS_STR+="," + PEERS_STR+="ctrl-$n=${PEER_ARRAY[$i]}" +done + +# Generate start script +cat > "$TOP/scripts/start.sh" << EOF +#!/bin/bash +set -e +NAME=\$1 +if [ -z "\$NAME" ]; then + echo "Usage: \$0 " + exit 1 +fi +CTRL_DIR=$TOP/\$NAME +if [ ! -d "\$CTRL_DIR/conf" ] || [ ! -d "\$CTRL_DIR/data" ]; then + echo "Error: \$CTRL_DIR/conf or \$CTRL_DIR/data does not exist" + exit 1 +fi +$BINARY --myname \$NAME --peers "$PEERS_STR" --workdir $TOP --logfile $TOP/logs/\$NAME.log --vcpus 4 \$VERBOSE +EOF +chmod +x "$TOP/scripts/start.sh" + +# Generate cleanup script +cat > "$TOP/scripts/cleanup.sh" << EOF +#!/bin/bash +set -e +rm -f $TOP/logs/*.log +for i in \$(seq 1 $N); do + rm -rf $TOP/ctrl-\$i/data/* +done +for i in \$(seq 2 $N); do + rm -rf $TOP/ctrl-\$i/conf/* +done +EOF +chmod +x "$TOP/scripts/cleanup.sh" + +echo "Deployed $N-node cluster to $TOP (tag: $TAG)"