|
|
@@ -1820,6 +1820,15 @@ func (r *Raft) flushAndReplicate() {
|
|
|
// ProposeWithForward proposes a command, forwarding to leader if necessary
|
|
|
// This is the recommended method for applications to use
|
|
|
func (r *Raft) ProposeWithForward(command []byte) (index uint64, term uint64, err error) {
|
|
|
+ r.mu.RLock()
|
|
|
+ // Check if we are part of the cluster
|
|
|
+ // If we are not in clusterNodes, we shouldn't accept data commands
|
|
|
+ if _, exists := r.clusterNodes[r.nodeID]; !exists {
|
|
|
+ r.mu.RUnlock()
|
|
|
+ return 0, 0, fmt.Errorf("node %s is not part of the cluster", r.nodeID)
|
|
|
+ }
|
|
|
+ r.mu.RUnlock()
|
|
|
+
|
|
|
// Try local propose first
|
|
|
idx, t, isLeader := r.Propose(command)
|
|
|
if isLeader {
|
|
|
@@ -1860,6 +1869,14 @@ func (r *Raft) ProposeWithForward(command []byte) (index uint64, term uint64, er
|
|
|
|
|
|
// ForwardGet forwards a get request to the leader
|
|
|
func (r *Raft) ForwardGet(key string) (string, bool, error) {
|
|
|
+ r.mu.RLock()
|
|
|
+ // Check if we are part of the cluster
|
|
|
+ if _, exists := r.clusterNodes[r.nodeID]; !exists {
|
|
|
+ r.mu.RUnlock()
|
|
|
+ return "", false, fmt.Errorf("node %s is not part of the cluster", r.nodeID)
|
|
|
+ }
|
|
|
+ r.mu.RUnlock()
|
|
|
+
|
|
|
// Check if we are leader (local read)
|
|
|
if r.state == Leader {
|
|
|
if r.config.GetHandler != nil {
|
|
|
@@ -2482,10 +2499,27 @@ func (r *Raft) applyConfigChange(entry *LogEntry) {
|
|
|
r.logger.Info("Successfully joined cluster with %d nodes", len(r.clusterNodes))
|
|
|
}
|
|
|
|
|
|
+ // Check if we have been removed from the cluster
|
|
|
+ if _, exists := r.clusterNodes[r.nodeID]; !exists {
|
|
|
+ r.logger.Warn("Node %s removed from cluster configuration", r.nodeID)
|
|
|
+ // We could shut down here, or just stay as a listener.
|
|
|
+ // For now, let's step down to Follower if we are not already.
|
|
|
+ if r.state != Follower {
|
|
|
+ r.becomeFollower(r.currentTerm)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
r.logger.Info("Applied config change at index %d, cluster now has %d nodes", entry.Index, len(r.clusterNodes))
|
|
|
|
|
|
// If we're the leader, update leader state
|
|
|
if r.state == Leader {
|
|
|
+ // Check if we are still in the cluster
|
|
|
+ if _, exists := r.clusterNodes[r.nodeID]; !exists {
|
|
|
+ r.logger.Warn("Leader removed from cluster (self), stepping down")
|
|
|
+ r.becomeFollower(r.currentTerm)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
// Initialize nextIndex/matchIndex for any new nodes
|
|
|
lastIndex := r.log.LastIndex()
|
|
|
for nodeID, addr := range r.clusterNodes {
|