From ddb7f9f9d5ffe369998fe988a9dce6b42692935f Mon Sep 17 00:00:00 2001 From: Derek Collison Date: Tue, 22 Aug 2023 17:45:19 -0700 Subject: [PATCH] Fix for a peer-remove of an R1 that would brick the stream. Signed-off-by: Derek Collison --- server/jetstream_cluster.go | 7 +++++- server/jetstream_super_cluster_test.go | 31 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/server/jetstream_cluster.go b/server/jetstream_cluster.go index 52d498bd..52ccf5bd 100644 --- a/server/jetstream_cluster.go +++ b/server/jetstream_cluster.go @@ -5184,7 +5184,12 @@ func (cc *jetStreamCluster) remapStreamAssignment(sa *streamAssignment, removePe return true } - // If we are here let's remove the peer at least. + // If R1 just return to avoid bricking the stream. + if sa.Group.node == nil || len(sa.Group.Peers) == 1 { + return false + } + + // If we are here let's remove the peer at least, as long as we are R>1 for i, peer := range sa.Group.Peers { if peer == removePeer { sa.Group.Peers[i] = sa.Group.Peers[len(sa.Group.Peers)-1] diff --git a/server/jetstream_super_cluster_test.go b/server/jetstream_super_cluster_test.go index d217f84c..f229d46a 100644 --- a/server/jetstream_super_cluster_test.go +++ b/server/jetstream_super_cluster_test.go @@ -4016,3 +4016,34 @@ func TestJetStreamSuperClusterMovingR1Stream(t *testing.T) { return nil }) } + +// https://github.com/nats-io/nats-server/issues/4396 +func TestJetStreamSuperClusterR1StreamPeerRemove(t *testing.T) { + sc := createJetStreamSuperCluster(t, 1, 3) + defer sc.shutdown() + + nc, js := jsClientConnect(t, sc.serverByName("C1-S1")) + defer nc.Close() + + _, err := js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"foo"}, + Replicas: 1, + }) + require_NoError(t, err) + + si, err := js.StreamInfo("TEST") + require_NoError(t, err) + + // Call peer remove on the only peer the leader. + resp, err := nc.Request(fmt.Sprintf(JSApiStreamRemovePeerT, "TEST"), []byte(`{"peer":"`+si.Cluster.Leader+`"}`), time.Second) + require_NoError(t, err) + var rpr JSApiStreamRemovePeerResponse + require_NoError(t, json.Unmarshal(resp.Data, &rpr)) + require_False(t, rpr.Success) + require_True(t, rpr.Error.ErrCode == 10075) + + // Stream should still be in place and useable. + _, err = js.StreamInfo("TEST") + require_NoError(t, err) +}