Files
pubsub/server/jetstream_cluster_1_test.go

11949 lines
337 KiB
Go

// Copyright 2020-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !skip_js_tests && !skip_js_cluster_tests
package server
import (
"bytes"
"context"
crand "crypto/rand"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"math"
"math/rand"
"os"
"path/filepath"
"reflect"
"slices"
"strconv"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/klauspost/compress/s2"
"github.com/nats-io/jwt/v2"
"github.com/nats-io/nats.go"
)
func TestJetStreamClusterConfig(t *testing.T) {
conf := createConfFile(t, []byte(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 16GB, max_file_store: 10TB, store_dir: '%s'}
cluster { listen: 127.0.0.1:-1 }
`))
check := func(errStr string) {
t.Helper()
opts, err := ProcessConfigFile(conf)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := NewServer(opts); err == nil || !strings.Contains(err.Error(), errStr) {
t.Fatalf("Expected an error of `%s`, got `%v`", errStr, err)
}
}
check("requires `server_name`")
conf = createConfFile(t, []byte(`
listen: 127.0.0.1:-1
server_name: "TEST"
jetstream: {max_mem_store: 16GB, max_file_store: 10TB, store_dir: '%s'}
cluster { listen: 127.0.0.1:-1 }
`))
check("requires `cluster.name`")
}
func TestJetStreamClusterLeader(t *testing.T) {
c := createJetStreamClusterExplicit(t, "JSC", 3)
defer c.shutdown()
// Kill our current leader and force an election.
c.leader().Shutdown()
c.waitOnLeader()
// Now killing our current leader should leave us leaderless.
c.leader().Shutdown()
c.expectNoLeader()
}
func TestJetStreamClusterExpand(t *testing.T) {
c := createJetStreamClusterExplicit(t, "JSC", 2)
defer c.shutdown()
c.addInNewServer()
c.waitOnPeerCount(3)
}
func TestJetStreamClusterAccountInfo(t *testing.T) {
c := createJetStreamClusterExplicit(t, "JSC", 3)
defer c.shutdown()
nc := clientConnectToServer(t, c.randomServer())
defer nc.Close()
reply := nats.NewInbox()
sub, _ := nc.SubscribeSync(reply)
if err := nc.PublishRequest(JSApiAccountInfo, reply, nil); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkSubsPending(t, sub, 1)
resp, _ := sub.NextMsg(0)
var info JSApiAccountInfoResponse
if err := json.Unmarshal(resp.Data, &info); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if info.JetStreamAccountStats == nil || info.Error != nil {
t.Fatalf("Did not receive correct response: %+v", info.Error)
}
// Make sure we only got 1 response.
// Technically this will always work since its a singleton service export.
if nmsgs, _, _ := sub.Pending(); nmsgs > 0 {
t.Fatalf("Expected only a single response, got %d more", nmsgs)
}
}
func TestJetStreamClusterStreamLimitWithAccountDefaults(t *testing.T) {
// 2MB memory, 8MB disk
c := createJetStreamClusterWithTemplate(t, jsClusterLimitsTempl, "R3L", 3)
defer c.shutdown()
// Client based API
s := c.randomNonLeader()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 2,
MaxBytes: 4 * 1024 * 1024,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST2",
Replicas: 2,
MaxBytes: 15 * 1024 * 1024,
})
require_Contains(t, err.Error(), "no suitable peers for placement", "insufficient storage")
}
func TestJetStreamClusterInfoRaftGroup(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R1S", 3)
defer c.shutdown()
s := c.randomNonLeader()
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Storage: nats.FileStorage,
Replicas: 3,
})
require_NoError(t, err)
nfoResp, err := nc.Request("$JS.API.STREAM.INFO.TEST", nil, time.Second)
require_NoError(t, err)
var si StreamInfo
err = json.Unmarshal(nfoResp.Data, &si)
require_NoError(t, err)
if si.Cluster == nil {
t.Fatalf("Expected cluster info, got none")
}
stream, err := acc.lookupStream("TEST")
require_NoError(t, err)
if si.Cluster.RaftGroup != stream.raftGroup().Name {
t.Fatalf("Expected raft group %q to equal %q", si.Cluster.RaftGroup, stream.raftGroup().Name)
}
var sscfg StreamConfig
rCfgData, err := os.ReadFile(filepath.Join(s.opts.StoreDir, "jetstream", "$SYS", "_js_", stream.raftGroup().Name, "meta.inf"))
require_NoError(t, err)
err = json.Unmarshal(rCfgData, &sscfg)
require_NoError(t, err)
if !reflect.DeepEqual(sscfg.Metadata, map[string]string{"account": "$G", "stream": "TEST", "type": "stream"}) {
t.Fatalf("Invalid raft stream metadata: %v", sscfg.Metadata)
}
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "DURABLE", Replicas: 3})
require_NoError(t, err)
consumer := stream.lookupConsumer("DURABLE")
var ci ConsumerInfo
nfoResp, err = nc.Request("$JS.API.CONSUMER.INFO.TEST.DURABLE", nil, time.Second)
require_NoError(t, err)
var cscfg ConsumerConfig
rCfgData, err = os.ReadFile(filepath.Join(s.opts.StoreDir, "jetstream", "$SYS", "_js_", consumer.raftGroup().Name, "meta.inf"))
require_NoError(t, err)
err = json.Unmarshal(rCfgData, &cscfg)
require_NoError(t, err)
if !reflect.DeepEqual(cscfg.Metadata, map[string]string{"account": "$G", "consumer": "DURABLE", "stream": "TEST", "type": "consumer"}) {
t.Fatalf("Invalid raft stream metadata: %v", cscfg.Metadata)
}
err = json.Unmarshal(nfoResp.Data, &ci)
require_NoError(t, err)
if ci.Cluster.RaftGroup != consumer.raftGroup().Name {
t.Fatalf("Expected raft group %q to equal %q", ci.Cluster.RaftGroup, consumer.raftGroup().Name)
}
}
func TestJetStreamClusterSingleReplicaStreams(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R1S", 3)
defer c.shutdown()
// Client based API
s := c.randomNonLeader()
nc, js := jsClientConnect(t, s)
defer nc.Close()
si, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.Cluster == nil {
t.Fatalf("Expected si to have cluster info")
}
// Send in 10 messages.
msg, toSend := []byte("Hello JS Clustering"), 10
for i := 0; i < toSend; i++ {
if _, err = js.Publish("foo", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
// Now grab info for this stream.
si, err = js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si == nil || si.Config.Name != "TEST" {
t.Fatalf("StreamInfo is not correct %+v", si)
}
// Check active state as well, shows that the owner answered.
if si.State.Msgs != uint64(toSend) {
t.Fatalf("Expected %d msgs, got bad state: %+v", toSend, si.State)
}
// Now create a consumer. This should be pinned to same server that our stream was allocated to.
// First do a normal sub.
sub, err := js.SubscribeSync("foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkSubsPending(t, sub, toSend)
// Now create a consumer as well.
ci, err := js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "dlc", AckPolicy: nats.AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if ci == nil || ci.Name != "dlc" || ci.Stream != "TEST" {
t.Fatalf("ConsumerInfo is not correct %+v", ci)
}
// Now make sure that if we kill and restart the server that this stream and consumer return.
sl := c.streamLeader("$G", "TEST")
sl.Shutdown()
c.restartServer(sl)
c.waitOnStreamLeader("$G", "TEST")
si, err = js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si == nil || si.Config.Name != "TEST" {
t.Fatalf("StreamInfo is not correct %+v", si)
}
// Now durable consumer.
c.waitOnConsumerLeader("$G", "TEST", "dlc")
if _, err = js.ConsumerInfo("TEST", "dlc"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
func TestJetStreamClusterMultiReplicaStreams(t *testing.T) {
c := createJetStreamClusterExplicit(t, "RNS", 5)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 3,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Send in 10 messages.
msg, toSend := []byte("Hello JS Clustering"), 10
for i := 0; i < toSend; i++ {
if _, err = js.Publish("foo", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
// Now grab info for this stream.
si, err := js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si == nil || si.Config.Name != "TEST" {
t.Fatalf("StreamInfo is not correct %+v", si)
}
// Check active state as well, shows that the owner answered.
if si.State.Msgs != uint64(toSend) {
t.Fatalf("Expected %d msgs, got bad state: %+v", toSend, si.State)
}
// Now create a consumer. This should be affinitize to the same set of servers as the stream.
// First do a normal sub.
sub, err := js.SubscribeSync("foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkSubsPending(t, sub, toSend)
// Now create a consumer as well.
ci, err := js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "dlc", AckPolicy: nats.AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if ci == nil || ci.Name != "dlc" || ci.Stream != "TEST" || ci.NumPending != uint64(toSend) {
t.Fatalf("ConsumerInfo is not correct %+v", ci)
}
}
func TestJetStreamClusterMultiReplicaStreamsDefaultFileMem(t *testing.T) {
const testConfig = `
listen: 127.0.0.1:-1
server_name: %s
jetstream: {store_dir: '%s'}
cluster {
name: %s
listen: 127.0.0.1:%d
routes = [%s]
}
`
c := createJetStreamClusterWithTemplate(t, testConfig, "RNS", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 3,
MaxBytes: 1024,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Send in 10 messages.
msg, toSend := []byte("Hello JS Clustering"), 10
for i := 0; i < toSend; i++ {
if _, err = js.Publish("foo", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
// Now grab info for this stream.
si, err := js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si == nil || si.Config.Name != "TEST" {
t.Fatalf("StreamInfo is not correct %+v", si)
}
// Check active state as well, shows that the owner answered.
if si.State.Msgs != uint64(toSend) {
t.Fatalf("Expected %d msgs, got bad state: %+v", toSend, si.State)
}
// Now create a consumer. This should be affinitize to the same set of servers as the stream.
// First do a normal sub.
sub, err := js.SubscribeSync("foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkSubsPending(t, sub, toSend)
// Now create a consumer as well.
ci, err := js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "dlc", AckPolicy: nats.AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if ci == nil || ci.Name != "dlc" || ci.Stream != "TEST" || ci.NumPending != uint64(toSend) {
t.Fatalf("ConsumerInfo is not correct %+v", ci)
}
}
func TestJetStreamClusterMemoryStore(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3M", 3)
defer c.shutdown()
// Client based API
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 3,
Storage: nats.MemoryStorage,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Send in 100 messages.
msg, toSend := []byte("Hello MemoryStore"), 100
for i := 0; i < toSend; i++ {
if _, err = js.Publish("foo", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
// Now grab info for this stream.
si, err := js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si == nil || si.Config.Name != "TEST" {
t.Fatalf("StreamInfo is not correct %+v", si)
}
if si.Cluster == nil || len(si.Cluster.Replicas) != 2 {
t.Fatalf("Cluster info is incorrect: %+v", si.Cluster)
}
// Check active state as well, shows that the owner answered.
if si.State.Msgs != uint64(toSend) {
t.Fatalf("Expected %d msgs, got bad state: %+v", toSend, si.State)
}
// Do a normal sub.
sub, err := js.SubscribeSync("foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkSubsPending(t, sub, toSend)
}
func TestJetStreamClusterDelete(t *testing.T) {
c := createJetStreamClusterExplicit(t, "RNS", 3)
defer c.shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "C22",
Subjects: []string{"foo", "bar", "baz"},
Replicas: 2,
Storage: nats.FileStorage,
MaxMsgs: 100,
}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Error adding stream: %v", err)
}
// Now create a consumer.
if _, err := js.AddConsumer("C22", &nats.ConsumerConfig{
Durable: "dlc",
AckPolicy: nats.AckExplicitPolicy,
}); err != nil {
t.Fatalf("Error adding consumer: %v", err)
}
// Now delete the consumer.
if err := js.DeleteConsumer("C22", "dlc"); err != nil {
t.Fatalf("Error deleting consumer: %v", err)
}
// Now delete the stream.
if err := js.DeleteStream("C22"); err != nil {
t.Fatalf("Error deleting stream: %v", err)
}
// This will get the current information about usage and limits for this account.
checkFor(t, time.Second, 15*time.Millisecond, func() error {
info, err := js.AccountInfo()
if err != nil {
return err
}
if info.Streams != 0 {
return fmt.Errorf("Expected no remaining streams, got %d", info.Streams)
}
return nil
})
}
func TestJetStreamClusterStreamPurge(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R5S", 5)
defer c.shutdown()
s := c.randomServer()
// Client based API
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 3,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msg, toSend := []byte("Hello JS Clustering"), 100
for i := 0; i < toSend; i++ {
if _, err = js.Publish("foo", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
// Now grab info for this stream.
si, err := js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Check active state as well, shows that the owner answered.
if si.State.Msgs != uint64(toSend) {
t.Fatalf("Expected %d msgs, got bad state: %+v", toSend, si.State)
}
// Now purge the stream.
if err := js.PurgeStream("TEST"); err != nil {
t.Fatalf("Unexpected purge error: %v", err)
}
si, err = js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs != 0 || si.State.FirstSeq != uint64(toSend+1) {
t.Fatalf("Expected no msgs, got: %+v", si.State)
}
}
func TestJetStreamClusterStreamUpdateSubjects(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 3,
}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Make sure we can update subjects.
cfg.Subjects = []string{"bar", "baz"}
si, err := js.UpdateStream(cfg)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si == nil {
t.Fatalf("Expected a stream info, got none")
}
if !reflect.DeepEqual(si.Config.Subjects, cfg.Subjects) {
t.Fatalf("Expected subjects to be updated: got %+v", si.Config.Subjects)
}
// Make sure it registered
js2, err := nc.JetStream(nats.MaxWait(250 * time.Millisecond))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err = js2.Publish("foo", nil); err == nil {
t.Fatalf("Expected this to fail")
}
if _, err = js2.Publish("baz", nil); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
func TestJetStreamClusterBadStreamUpdate(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 3,
}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msg, toSend := []byte("Keep Me"), 50
for i := 0; i < toSend; i++ {
if _, err := js.Publish("foo", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
// Make sure a bad update will not remove our stream.
cfg.Subjects = []string{"foo..bar"}
if _, err := js.UpdateStream(cfg); err == nil || err == nats.ErrTimeout {
t.Fatalf("Expected error but got none or timeout")
}
// Make sure we did not delete our original stream.
si, err := js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !reflect.DeepEqual(si.Config.Subjects, []string{"foo", "bar"}) {
t.Fatalf("Expected subjects to be original ones, got %+v", si.Config.Subjects)
}
}
func TestJetStreamClusterConsumerRedeliveredInfo(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
cfg := &nats.StreamConfig{Name: "TEST"}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.Publish("TEST", []byte("CI")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
sub, _ := nc.SubscribeSync("R")
sub.AutoUnsubscribe(2)
ci, err := js.AddConsumer("TEST", &nats.ConsumerConfig{
DeliverSubject: "R",
AckPolicy: nats.AckExplicitPolicy,
AckWait: 100 * time.Millisecond,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkSubsPending(t, sub, 2)
sub.Unsubscribe()
ci, err = js.ConsumerInfo("TEST", ci.Name)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if ci.NumRedelivered != 1 {
t.Fatalf("Expected 1 redelivered, got %d", ci.NumRedelivered)
}
}
func TestJetStreamClusterConsumerState(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 5)
defer c.shutdown()
s := c.randomServer()
// Client based API
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 3,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msg, toSend := []byte("Hello JS Clustering"), 10
for i := 0; i < toSend; i++ {
if _, err = js.Publish("foo", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
// Make sure we are not connected to any of the stream servers so that we do not do client reconnect
// when we take out the consumer leader.
if s.JetStreamIsStreamAssigned("$G", "TEST") {
nc.Close()
for _, ns := range c.servers {
if !ns.JetStreamIsStreamAssigned("$G", "TEST") {
s = ns
nc, js = jsClientConnect(t, s)
defer nc.Close()
break
}
}
}
sub, err := js.PullSubscribe("foo", "dlc")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Pull 5 messages and ack.
for _, m := range fetchMsgs(t, sub, 5, 5*time.Second) {
m.AckSync()
}
// Let state propagate for exact comparison below.
time.Sleep(200 * time.Millisecond)
ci, err := sub.ConsumerInfo()
if err != nil {
t.Fatalf("Unexpected error getting consumer info: %v", err)
}
if ci.AckFloor.Consumer != 5 {
t.Fatalf("Expected ack floor of %d, got %d", 5, ci.AckFloor.Consumer)
}
c.consumerLeader("$G", "TEST", "dlc").Shutdown()
c.waitOnConsumerLeader("$G", "TEST", "dlc")
nci, err := sub.ConsumerInfo()
if err != nil {
t.Fatalf("Unexpected error getting consumer info: %v", err)
}
// nil out timestamp for better comparison
nci.Delivered.Last, ci.Delivered.Last = nil, nil
if nci.Delivered != ci.Delivered {
t.Fatalf("Consumer delivered did not match after leader switch, wanted %+v, got %+v", ci.Delivered, nci.Delivered)
}
nci.AckFloor.Last, ci.AckFloor.Last = nil, nil
if nci.AckFloor != ci.AckFloor {
t.Fatalf("Consumer ackfloor did not match after leader switch, wanted %+v, got %+v", ci.AckFloor, nci.AckFloor)
}
// Now make sure we can receive new messages.
// Pull last 5.
for _, m := range fetchMsgs(t, sub, 5, 5*time.Second) {
m.AckSync()
}
nci, _ = sub.ConsumerInfo()
if nci.Delivered.Consumer != 10 || nci.Delivered.Stream != 10 {
t.Fatalf("Received bad delivered: %+v", nci.Delivered)
}
if nci.AckFloor.Consumer != 10 || nci.AckFloor.Stream != 10 {
t.Fatalf("Received bad ackfloor: %+v", nci.AckFloor)
}
if nci.NumAckPending != 0 {
t.Fatalf("Received bad ackpending: %+v", nci.NumAckPending)
}
}
func TestJetStreamClusterFullConsumerState(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
s := c.randomServer()
// Client based API
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 3,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msg, toSend := []byte("Hello JS Clustering"), 10
for i := 0; i < toSend; i++ {
if _, err = js.Publish("foo", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
sub, err := js.PullSubscribe("foo", "dlc")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
fetchMsgs(t, sub, 1, 5*time.Second)
// Now purge the stream.
if err := js.PurgeStream("TEST"); err != nil {
t.Fatalf("Unexpected purge error: %v", err)
}
}
func TestJetStreamClusterMetaSnapshotsAndCatchup(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Shut one down.
rs := c.randomServer()
rs.Shutdown()
c.waitOnLeader()
s := c.leader()
// Client based API
nc, js := jsClientConnect(t, s)
defer nc.Close()
numStreams := 4
// Create 4 streams
// FIXME(dlc) - R2 make sure we place properly.
for i := 0; i < numStreams; i++ {
sn := fmt.Sprintf("T-%d", i+1)
_, err := js.AddStream(&nats.StreamConfig{Name: sn})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
c.leader().JetStreamSnapshotMeta()
rs = c.restartServer(rs)
c.checkClusterFormed()
c.waitOnServerCurrent(rs)
rs.Shutdown()
c.waitOnLeader()
for i := 0; i < numStreams; i++ {
sn := fmt.Sprintf("T-%d", i+1)
err := js.DeleteStream(sn)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
rs = c.restartServer(rs)
c.checkClusterFormed()
c.waitOnServerCurrent(rs)
}
func TestJetStreamClusterMetaSnapshotsMultiChange(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 2)
defer c.shutdown()
s := c.leader()
// Client based API
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Add in 2 streams with 1 consumer each.
if _, err := js.AddStream(&nats.StreamConfig{Name: "S1"}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c.waitOnStreamLeader(globalAccountName, "S1")
_, err := js.AddConsumer("S1", &nats.ConsumerConfig{Durable: "S1C1", AckPolicy: nats.AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c.waitOnConsumerLeader(globalAccountName, "S1", "S1C1")
if _, err = js.AddStream(&nats.StreamConfig{Name: "S2"}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c.waitOnStreamLeader(globalAccountName, "S2")
_, err = js.AddConsumer("S2", &nats.ConsumerConfig{Durable: "S2C1", AckPolicy: nats.AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c.waitOnConsumerLeader(globalAccountName, "S2", "S2C1")
// Add in a new server to the group. This way we know we can delete the original streams and consumers.
rs := c.addInNewServer()
c.waitOnServerCurrent(rs)
rsn := rs.Name()
// Shut it down.
rs.Shutdown()
// Wait for the peer to be removed.
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
for _, p := range s.JetStreamClusterPeers() {
if p == rsn {
return fmt.Errorf("Old server still in peer set")
}
}
return nil
})
// We want to make changes here that test each delta scenario for the meta snapshots.
// Add new stream and consumer.
if _, err = js.AddStream(&nats.StreamConfig{Name: "S3"}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c.waitOnStreamLeader(globalAccountName, "S3")
_, err = js.AddConsumer("S3", &nats.ConsumerConfig{Durable: "S3C1", AckPolicy: nats.AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c.waitOnConsumerLeader(globalAccountName, "S3", "S3C1")
// Delete stream S2
resp, _ := nc.Request(fmt.Sprintf(JSApiStreamDeleteT, "S2"), nil, time.Second)
var dResp JSApiStreamDeleteResponse
if err := json.Unmarshal(resp.Data, &dResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !dResp.Success || dResp.Error != nil {
t.Fatalf("Got a bad response %+v", dResp.Error)
}
// Delete the consumer on S1 but add another.
resp, err = nc.Request(fmt.Sprintf(JSApiConsumerDeleteT, "S1", "S1C1"), nil, time.Second)
require_NoError(t, err)
require_NotNil(t, resp)
var cdResp JSApiConsumerDeleteResponse
if err = json.Unmarshal(resp.Data, &cdResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !cdResp.Success || cdResp.Error != nil {
t.Fatalf("Got a bad response %+v", cdResp)
}
// Add new consumer on S1
_, err = js.AddConsumer("S1", &nats.ConsumerConfig{Durable: "S1C2", AckPolicy: nats.AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c.waitOnConsumerLeader(globalAccountName, "S1", "S1C2")
cl := c.leader()
cl.JetStreamSnapshotMeta()
c.waitOnServerCurrent(cl)
rs = c.restartServer(rs)
c.checkClusterFormed()
c.waitOnServerCurrent(rs)
}
func TestJetStreamClusterStreamSynchedTimeStamps(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "foo", Storage: nats.MemoryStorage, Replicas: 3})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err = js.Publish("foo", []byte("TSS")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
// Grab the message and timestamp from our current leader
sub, err := js.SubscribeSync("foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
meta, _ := m.Metadata()
sub.Unsubscribe()
sl := c.streamLeader("$G", "foo")
sl.Shutdown()
c.waitOnLeader()
c.waitOnStreamLeader("$G", "foo")
nc, js = jsClientConnect(t, c.leader())
defer nc.Close()
sm, err := js.GetMsg("foo", 1)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !sm.Time.Equal(meta.Timestamp) {
t.Fatalf("Expected same timestamps, got %v vs %v", sm.Time, meta.Timestamp)
}
}
// Test to mimic what R.I. was seeing.
func TestJetStreamClusterRestoreSingleConsumer(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "foo"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err = js.Publish("foo", []byte("TSS")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
sub, err := js.SubscribeSync("foo", nats.Durable("dlc"))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if m, err := sub.NextMsg(time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
} else {
m.AckSync()
}
c.stopAll()
c.restartAll()
c.waitOnLeader()
c.waitOnStreamLeader("$G", "foo")
s = c.randomServer()
nc, js = jsClientConnect(t, s)
defer nc.Close()
var names []string
for name := range js.StreamNames() {
names = append(names, name)
}
if len(names) != 1 {
t.Fatalf("Expected only 1 stream but got %d", len(names))
}
// Now do detailed version.
var infos []*nats.StreamInfo
for info := range js.StreamsInfo() {
infos = append(infos, info)
}
if len(infos) != 1 {
t.Fatalf("Expected 1 stream but got %d", len(infos))
}
si, err := js.StreamInfo("foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si == nil || si.Config.Name != "foo" {
t.Fatalf("StreamInfo is not correct %+v", si)
}
// Now check for consumer.
names = names[:0]
for name := range js.ConsumerNames("foo") {
names = append(names, name)
}
if len(names) != 1 {
t.Fatalf("Expected 1 consumer but got %d", len(names))
}
}
func TestJetStreamClusterMaxBytesForStream(t *testing.T) {
// Has max_file_store of 2GB
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
info, err := js.AccountInfo()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Make sure we still are dynamic.
if info.Limits.MaxStore != -1 || info.Limits.MaxMemory != -1 {
t.Fatalf("Expected dynamic limits for the account, got %+v\n", info.Limits)
}
// Stream config.
cfg := &nats.StreamConfig{
Name: "TEST",
Replicas: 2,
MaxBytes: 2 * 1024 * 1024 * 1024, // 2GB
}
_, err = js.AddStream(cfg)
require_NoError(t, err)
// Make sure going over the single server limit though is enforced (for now).
cfg.Name = "TEST2"
cfg.MaxBytes *= 2
_, err = js.AddStream(cfg)
require_Contains(t, err.Error(), "no suitable peers for placement")
}
func TestJetStreamClusterStreamPublishWithActiveConsumers(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
s := c.randomServer()
// Client based API
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "foo", Replicas: 3})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err = js.Publish("foo", []byte("TSS")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
sub, err := js.SubscribeSync("foo", nats.Durable("dlc"))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// FIXME(dlc) - Need to track this down.
c.waitOnConsumerLeader("$G", "foo", "dlc")
if m, err := sub.NextMsg(time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
} else {
m.AckSync()
}
// Send 10 messages.
for i := 1; i <= 10; i++ {
payload := []byte(fmt.Sprintf("MSG-%d", i))
if _, err = js.Publish("foo", payload); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
checkSubsPending(t, sub, 10)
// Sanity check for duplicate deliveries..
if nmsgs, _, _ := sub.Pending(); nmsgs > 10 {
t.Fatalf("Expected only %d responses, got %d more", 10, nmsgs)
}
for i := 1; i <= 10; i++ {
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
payload := []byte(fmt.Sprintf("MSG-%d", i))
if !bytes.Equal(m.Data, payload) {
t.Fatalf("Did not get expected msg, expected %q, got %q", payload, m.Data)
}
}
var ci *nats.ConsumerInfo
checkFor(t, time.Second, 100*time.Millisecond, func() error {
ci, err = sub.ConsumerInfo()
if err != nil {
return fmt.Errorf("Unexpected error getting consumer info: %v", err)
}
if ci.Delivered.Stream != 11 {
return fmt.Errorf("expected delivered stream sequence to be %d, got %d", 11, ci.Delivered.Stream)
}
return nil
})
c.consumerLeader("$G", "foo", "dlc").Shutdown()
c.waitOnConsumerLeader("$G", "foo", "dlc")
ci2, err := sub.ConsumerInfo()
if err != nil {
t.Fatalf("Unexpected error getting consumer info: %v", err)
}
ci.Cluster = nil
ci2.Cluster = nil
// nil out timestamp for better comparison
ci.Delivered.Last, ci2.Delivered.Last = nil, nil
ci.AckFloor.Last, ci2.AckFloor.Last = nil, nil
if !reflect.DeepEqual(ci, ci2) {
t.Fatalf("Consumer info did not match: %+v vs %+v", ci, ci2)
}
// In case the server above was also stream leader.
c.waitOnStreamLeader("$G", "foo")
// Now send more..
// Send 10 more messages.
for i := 11; i <= 20; i++ {
payload := []byte(fmt.Sprintf("MSG-%d", i))
if _, err = js.Publish("foo", payload); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
checkSubsPending(t, sub, 10)
// Sanity check for duplicate deliveries..
if nmsgs, _, _ := sub.Pending(); nmsgs > 10 {
t.Fatalf("Expected only %d responses, got %d more", 10, nmsgs)
}
for i := 11; i <= 20; i++ {
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
payload := []byte(fmt.Sprintf("MSG-%d", i))
if !bytes.Equal(m.Data, payload) {
t.Fatalf("Did not get expected msg, expected %q, got %q", payload, m.Data)
}
}
}
func TestJetStreamClusterStreamOverlapSubjects(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST2", Subjects: []string{"foo"}}); err == nil || err == nats.ErrTimeout {
t.Fatalf("Expected error but got none or timeout: %v", err)
}
// Now grab list of streams and make sure the second is not there.
var names []string
for name := range js.StreamNames() {
names = append(names, name)
}
if len(names) != 1 {
t.Fatalf("Expected only 1 stream but got %d", len(names))
}
// Now do a detailed version.
var infos []*nats.StreamInfo
for info := range js.StreamsInfo() {
infos = append(infos, info)
}
if len(infos) != 1 {
t.Fatalf("Expected only 1 stream but got %d", len(infos))
}
}
func TestJetStreamClusterStreamInfoList(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
createStream := func(name string) {
t.Helper()
if _, err := js.AddStream(&nats.StreamConfig{Name: name}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
createStream("foo")
createStream("bar")
createStream("baz")
sendBatch := func(subject string, n int) {
t.Helper()
// Send a batch to a given subject.
for i := 0; i < n; i++ {
if _, err := js.Publish(subject, []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
}
sendBatch("foo", 10)
sendBatch("bar", 22)
sendBatch("baz", 33)
// Now get the stream list info.
var infos []*nats.StreamInfo
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
infos = infos[:0]
for info := range js.StreamsInfo() {
infos = append(infos, info)
}
if len(infos) != 3 {
return fmt.Errorf("StreamInfo expected 3 results, got %d", len(infos))
}
return nil
})
for _, si := range infos {
switch si.Config.Name {
case "foo":
if si.State.Msgs != 10 {
t.Fatalf("Expected %d msgs but got %d", 10, si.State.Msgs)
}
case "bar":
if si.State.Msgs != 22 {
t.Fatalf("Expected %d msgs but got %d", 22, si.State.Msgs)
}
case "baz":
if si.State.Msgs != 33 {
t.Fatalf("Expected %d msgs but got %d", 33, si.State.Msgs)
}
}
}
}
func TestJetStreamClusterConsumerInfoList(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 3}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Place messages so we can generate consumer state.
for i := 0; i < 10; i++ {
if _, err := js.Publish("TEST", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
createConsumer := func(name string) *nats.Subscription {
t.Helper()
sub, err := js.PullSubscribe("TEST", name)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
return sub
}
subFoo := createConsumer("foo")
subBar := createConsumer("bar")
subBaz := createConsumer("baz")
// Place consumers in various states.
for _, ss := range []struct {
sub *nats.Subscription
fetch int
ack int
}{
{subFoo, 4, 2},
{subBar, 2, 0},
{subBaz, 8, 6},
} {
msgs := fetchMsgs(t, ss.sub, ss.fetch, 5*time.Second)
for i := 0; i < ss.ack; i++ {
msgs[i].AckSync()
}
}
// Now get the consumer list info.
var infos []*nats.ConsumerInfo
for info := range js.ConsumersInfo("TEST") {
infos = append(infos, info)
}
if len(infos) != 3 {
t.Fatalf("ConsumerInfo expected 3 results, got %d", len(infos))
}
for _, ci := range infos {
switch ci.Name {
case "foo":
if ci.Delivered.Consumer != 4 {
t.Fatalf("Expected %d delivered but got %d", 4, ci.Delivered.Consumer)
}
if ci.AckFloor.Consumer != 2 {
t.Fatalf("Expected %d for ack floor but got %d", 2, ci.AckFloor.Consumer)
}
case "bar":
if ci.Delivered.Consumer != 2 {
t.Fatalf("Expected %d delivered but got %d", 2, ci.Delivered.Consumer)
}
if ci.AckFloor.Consumer != 0 {
t.Fatalf("Expected %d for ack floor but got %d", 0, ci.AckFloor.Consumer)
}
case "baz":
if ci.Delivered.Consumer != 8 {
t.Fatalf("Expected %d delivered but got %d", 8, ci.Delivered.Consumer)
}
if ci.AckFloor.Consumer != 6 {
t.Fatalf("Expected %d for ack floor but got %d", 6, ci.AckFloor.Consumer)
}
}
}
}
func TestJetStreamClusterStreamUpdate(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
sc := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
MaxMsgs: 10,
Discard: DiscardNew,
}
if _, err := js.AddStream(sc); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for i := 1; i <= int(sc.MaxMsgs); i++ {
msg := []byte(fmt.Sprintf("HELLO JSC-%d", i))
if _, err := js.Publish("foo", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
// Expect error here.
if _, err := js.Publish("foo", []byte("fail")); err == nil {
t.Fatalf("Expected publish to fail")
}
// Now update MaxMsgs, select non-leader
s = c.randomNonStreamLeader("$G", "TEST")
nc, js = jsClientConnect(t, s)
defer nc.Close()
sc.MaxMsgs = 20
si, err := js.UpdateStream(sc)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.Config.MaxMsgs != 20 {
t.Fatalf("Expected to have config updated with max msgs of %d, got %d", 20, si.Config.MaxMsgs)
}
// Do one that will fail. Wait and make sure we only are getting one response.
sc.Name = "TEST22"
rsub, _ := nc.SubscribeSync(nats.NewInbox())
defer rsub.Unsubscribe()
nc.Flush()
req, _ := json.Marshal(sc)
if err := nc.PublishRequest(fmt.Sprintf(JSApiStreamUpdateT, "TEST"), rsub.Subject, req); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Wait incase more than one reply sent.
time.Sleep(250 * time.Millisecond)
if nmsgs, _, _ := rsub.Pending(); err != nil || nmsgs != 1 {
t.Fatalf("Expected only one response, got %d", nmsgs)
}
m, err := rsub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Error getting message: %v", err)
}
var scResp JSApiStreamCreateResponse
if err := json.Unmarshal(m.Data, &scResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if scResp.StreamInfo != nil || scResp.Error == nil {
t.Fatalf("Did not receive correct response: %+v", scResp)
}
}
func TestJetStreamClusterStreamExtendedUpdates(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
updateStream := func() *nats.StreamInfo {
si, err := js.UpdateStream(cfg)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
return si
}
// Subjects can be updated
cfg.Subjects = []string{"bar", "baz"}
if si := updateStream(); !reflect.DeepEqual(si.Config.Subjects, cfg.Subjects) {
t.Fatalf("Did not get expected stream info: %+v", si)
}
// Mirror changes are not supported for now
cfg.Subjects = nil
cfg.Mirror = &nats.StreamSource{Name: "ORDERS"}
_, err := js.UpdateStream(cfg)
require_Error(t, err, NewJSStreamMirrorNotUpdatableError())
}
func TestJetStreamClusterDoubleAdd(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R32", 2)
defer c.shutdown()
s := c.randomServer()
// Client based API
nc, js := jsClientConnect(t, s)
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 2}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Streams should allow double add.
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 2}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Check Consumers.
cfg := &nats.ConsumerConfig{Durable: "dlc", AckPolicy: nats.AckExplicitPolicy}
if _, err := js.AddConsumer("TEST", cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Check double add ok.
if _, err := js.AddConsumer("TEST", cfg); err != nil {
t.Fatalf("Expected no error but got: %v", err)
}
}
func TestJetStreamClusterDefaultMaxAckPending(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R32", 2)
defer c.shutdown()
s := c.randomServer()
// Client based API
nc, js := jsClientConnect(t, s)
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 2}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Do Consumers too.
cfg := &nats.ConsumerConfig{Durable: "dlc", AckPolicy: nats.AckExplicitPolicy}
ci, err := js.AddConsumer("TEST", cfg)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Check that we have a default set now for the max ack pending.
if ci.Config.MaxAckPending != JsDefaultMaxAckPending {
t.Fatalf("Expected a default for max ack pending of %d, got %d", JsDefaultMaxAckPending, ci.Config.MaxAckPending)
}
}
func TestJetStreamClusterStreamNormalCatchup(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 3,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
toSend := 10
for i := 1; i <= toSend; i++ {
msg := []byte(fmt.Sprintf("HELLO JSC-%d", i))
if _, err = js.Publish("foo", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
sl := c.streamLeader("$G", "TEST")
sl.Shutdown()
c.waitOnStreamLeader("$G", "TEST")
// Send 10 more while one replica offline.
for i := toSend; i <= toSend*2; i++ {
msg := []byte(fmt.Sprintf("HELLO JSC-%d", i))
if _, err = js.Publish("foo", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
// Delete the first from the second batch.
dreq := JSApiMsgDeleteRequest{Seq: uint64(toSend)}
dreqj, err := json.Marshal(dreq)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
resp, _ := nc.Request(fmt.Sprintf(JSApiMsgDeleteT, "TEST"), dreqj, time.Second)
var delMsgResp JSApiMsgDeleteResponse
if err = json.Unmarshal(resp.Data, &delMsgResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !delMsgResp.Success || delMsgResp.Error != nil {
t.Fatalf("Got a bad response %+v", delMsgResp.Error)
}
sl = c.restartServer(sl)
c.checkClusterFormed()
c.waitOnServerCurrent(sl)
c.waitOnStreamCurrent(sl, "$G", "TEST")
}
func TestJetStreamClusterStreamSnapshotCatchup(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
s := c.randomServer()
// Client based API
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
pseq := uint64(1)
sendBatch := func(n int) {
t.Helper()
// Send a batch.
for i := 0; i < n; i++ {
msg := []byte(fmt.Sprintf("HELLO JSC-%d", pseq))
if _, err = js.Publish("foo", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
pseq++
}
}
sendBatch(2)
sl := c.streamLeader("$G", "TEST")
sl.Shutdown()
c.waitOnStreamLeader("$G", "TEST")
sendBatch(100)
deleteMsg := func(seq uint64) {
if err := js.DeleteMsg("TEST", seq); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
// Delete the first from the second batch.
deleteMsg(pseq / 2)
// Delete the next one too.
deleteMsg(pseq/2 + 1)
nsl := c.streamLeader("$G", "TEST")
nsl.JetStreamSnapshotStream("$G", "TEST")
// Do some activity post snapshot as well.
// Delete next to last.
deleteMsg(pseq - 2)
// Send another batch.
sendBatch(100)
mset, err := nsl.GlobalAccount().lookupStream("TEST")
require_NoError(t, err)
ostate := mset.stateWithDetail(true)
sl = c.restartServer(sl)
c.checkClusterFormed()
c.waitOnServerCurrent(sl)
c.waitOnStreamCurrent(sl, "$G", "TEST")
mset, err = sl.GlobalAccount().lookupStream("TEST")
require_NoError(t, err)
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
if nstate := mset.stateWithDetail(true); !reflect.DeepEqual(ostate, nstate) {
return fmt.Errorf("States do not match after recovery: %+v vs %+v", ostate, nstate)
}
return nil
})
}
func TestJetStreamClusterDeleteMsg(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// R=1 make sure delete works.
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
toSend := 10
for i := 1; i <= toSend; i++ {
msg := []byte(fmt.Sprintf("HELLO JSC-%d", i))
if _, err = js.Publish("TEST", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
deleteMsg := func(seq uint64) {
if err := js.DeleteMsg("TEST", seq); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
deleteMsg(1)
// Also make sure purge of R=1 works too.
if err := js.PurgeStream("TEST"); err != nil {
t.Fatalf("Unexpected purge error: %v", err)
}
}
func TestJetStreamClusterDeleteMsgAndRestart(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// R=1 make sure delete works.
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 2})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
toSend := 10
for i := 1; i <= toSend; i++ {
msg := []byte(fmt.Sprintf("HELLO JSC-%d", i))
if _, err = js.Publish("TEST", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
deleteMsg := func(seq uint64) {
if err := js.DeleteMsg("TEST", seq); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
deleteMsg(1)
c.stopAll()
c.restartAll()
c.waitOnStreamLeader("$G", "TEST")
}
func TestJetStreamClusterStreamSnapshotCatchupWithPurge(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R5S", 5)
defer c.shutdown()
s := c.randomServer()
// Client based API
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sl := c.streamLeader("$G", "TEST")
sl.Shutdown()
c.waitOnStreamLeader("$G", "TEST")
toSend := 10
for i := 0; i < toSend; i++ {
if _, err = js.Publish("foo", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
nsl := c.streamLeader("$G", "TEST")
if err := nsl.JetStreamSnapshotStream("$G", "TEST"); err != nil {
t.Fatalf("Error snapshotting stream: %v", err)
}
time.Sleep(250 * time.Millisecond)
sl = c.restartServer(sl)
c.checkClusterFormed()
// Now purge the stream while we are recovering.
if err := js.PurgeStream("TEST"); err != nil {
t.Fatalf("Unexpected purge error: %v", err)
}
c.waitOnServerCurrent(sl)
c.waitOnStreamCurrent(sl, "$G", "TEST")
nsl.Shutdown()
c.waitOnStreamLeader("$G", "TEST")
if _, err := js.StreamInfo("TEST"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
func TestJetStreamClusterExtendedStreamInfo(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
toSend := 50
for i := 0; i < toSend; i++ {
if _, err = js.Publish("foo", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
leader := c.streamLeader("$G", "TEST").Name()
si, err := js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.Cluster == nil {
t.Fatalf("Expected cluster info")
}
if si.Cluster.Name != c.name {
t.Fatalf("Expected cluster name of %q, got %q", c.name, si.Cluster.Name)
}
if si.Cluster.Leader != leader {
t.Fatalf("Expected leader of %q, got %q", leader, si.Cluster.Leader)
}
if len(si.Cluster.Replicas) != 2 {
t.Fatalf("Expected %d replicas, got %d", 2, len(si.Cluster.Replicas))
}
// Make sure that returned array is ordered
for i := 0; i < 50; i++ {
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_True(t, len(si.Cluster.Replicas) == 2)
s1 := si.Cluster.Replicas[0].Name
s2 := si.Cluster.Replicas[1].Name
if s1 > s2 {
t.Fatalf("Expected replicas to be ordered, got %s then %s", s1, s2)
}
}
// We may need to wait a bit for peers to catch up.
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
return checkState(t, c, "$G", "TEST")
})
// Shutdown the leader.
oldLeader := c.streamLeader("$G", "TEST")
oldLeader.Shutdown()
c.waitOnStreamLeader("$G", "TEST")
// Re-request.
leader = c.streamLeader("$G", "TEST").Name()
si, err = js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.Cluster == nil {
t.Fatalf("Expected cluster info")
}
if si.Cluster.Leader != leader {
t.Fatalf("Expected leader of %q, got %q", leader, si.Cluster.Leader)
}
if len(si.Cluster.Replicas) != 2 {
t.Fatalf("Expected %d replicas, got %d", 2, len(si.Cluster.Replicas))
}
for _, peer := range si.Cluster.Replicas {
if peer.Name == oldLeader.Name() {
if peer.Current {
t.Fatalf("Expected old leader to be reported as not current: %+v", peer)
}
} else if !peer.Current {
t.Fatalf("Expected replica to be current: %+v", peer)
}
}
// Now send a few more messages then restart the oldLeader.
for i := 0; i < 10; i++ {
if _, err = js.Publish("foo", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
oldLeader = c.restartServer(oldLeader)
c.checkClusterFormed()
c.waitOnStreamLeader("$G", "TEST")
c.waitOnStreamCurrent(oldLeader, "$G", "TEST")
// Re-request.
leader = c.streamLeader("$G", "TEST").Name()
si, err = js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.Cluster == nil {
t.Fatalf("Expected cluster info")
}
if si.Cluster.Leader != leader {
t.Fatalf("Expected leader of %q, got %q", leader, si.Cluster.Leader)
}
if len(si.Cluster.Replicas) != 2 {
t.Fatalf("Expected %d replicas, got %d", 2, len(si.Cluster.Replicas))
}
// We may need to wait a bit for peers to catch up.
checkFor(t, 10*time.Second, 100*time.Millisecond, func() error {
return checkState(t, c, "$G", "TEST")
})
nc, js = jsClientConnect(t, c.randomServer())
defer nc.Close()
// Now do consumer.
sub, err := js.PullSubscribe("foo", "dlc")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer sub.Unsubscribe()
fetchMsgs(t, sub, 10, 5*time.Second)
leader = c.consumerLeader("$G", "TEST", "dlc").Name()
ci, err := sub.ConsumerInfo()
if err != nil {
t.Fatalf("Unexpected error getting consumer info: %v", err)
}
if ci.Cluster.Leader != leader {
t.Fatalf("Expected leader of %q, got %q", leader, ci.Cluster.Leader)
}
if len(ci.Cluster.Replicas) != 2 {
t.Fatalf("Expected %d replicas, got %d", 2, len(ci.Cluster.Replicas))
}
checkFor(t, 10*time.Second, 100*time.Millisecond, func() error {
return checkState(t, c, "$G", "TEST")
})
}
func TestJetStreamClusterExtendedStreamInfoSingleReplica(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
toSend := 50
for i := 0; i < toSend; i++ {
if _, err = js.Publish("foo", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
leader := c.streamLeader("$G", "TEST").Name()
si, err := js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.Cluster == nil {
t.Fatalf("Expected cluster info")
}
if si.Cluster.Name != c.name {
t.Fatalf("Expected cluster name of %q, got %q", c.name, si.Cluster.Name)
}
if si.Cluster.Leader != leader {
t.Fatalf("Expected leader of %q, got %q", leader, si.Cluster.Leader)
}
if len(si.Cluster.Replicas) != 0 {
t.Fatalf("Expected no replicas but got %d", len(si.Cluster.Replicas))
}
// Make sure we can grab consumer lists from any
var infos []*nats.ConsumerInfo
for info := range js.ConsumersInfo("TEST") {
infos = append(infos, info)
}
if len(infos) != 0 {
t.Fatalf("ConsumerInfo expected no paged results, got %d", len(infos))
}
// Now add in a consumer.
cfg := &nats.ConsumerConfig{Durable: "dlc", AckPolicy: nats.AckExplicitPolicy}
if _, err := js.AddConsumer("TEST", cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
infos = infos[:0]
for info := range js.ConsumersInfo("TEST") {
infos = append(infos, info)
}
if len(infos) != 1 {
t.Fatalf("ConsumerInfo expected 1 result, got %d", len(infos))
}
// Now do direct names list as well.
var names []string
for name := range js.ConsumerNames("TEST") {
names = append(names, name)
}
if len(names) != 1 {
t.Fatalf("Expected only 1 consumer but got %d", len(names))
}
}
func TestJetStreamClusterInterestRetention(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "foo", Retention: nats.InterestPolicy, Replicas: 3})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sub, err := js.SubscribeSync("foo", nats.Durable("dlc"))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sl := c.streamLeader("$G", "foo")
cl := c.consumerLeader("$G", "foo", "dlc")
if sl == cl {
_, err := nc.Request(fmt.Sprintf(JSApiStreamLeaderStepDownT, "foo"), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c.waitOnStreamLeader("$G", "foo")
}
if _, err = js.Publish("foo", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Unexpected error getting msg: %v", err)
}
m.AckSync()
waitForZero := func() {
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
si, err := js.StreamInfo("foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs != 0 {
return fmt.Errorf("Expected 0 msgs, got state: %+v", si.State)
}
return nil
})
}
waitForZero()
// Add in 50 messages.
for i := 0; i < 50; i++ {
if _, err = js.Publish("foo", []byte("more")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
checkSubsPending(t, sub, 50)
// Now delete the consumer and make sure the stream goes to zero.
if err := js.DeleteConsumer("foo", "dlc"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
waitForZero()
}
// https://github.com/nats-io/nats-server/issues/2243
func TestJetStreamClusterWorkQueueRetention(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "FOO",
Subjects: []string{"foo.*"},
Replicas: 2,
Retention: nats.WorkQueuePolicy,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sub, err := js.PullSubscribe("foo.test", "test")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err = js.Publish("foo.test", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
si, err := js.StreamInfo("FOO")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs != 1 {
t.Fatalf("Expected 1 msg, got state: %+v", si.State)
}
// Fetch from our pull consumer and ack.
for _, m := range fetchMsgs(t, sub, 1, 5*time.Second) {
m.AckSync()
}
// Make sure the messages are removed.
checkFor(t, 5*time.Second, 100*time.Millisecond, func() error {
si, err := js.StreamInfo("FOO")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs != 0 {
return fmt.Errorf("Expected 0 msgs, got state: %+v", si.State)
}
return nil
})
}
func TestJetStreamClusterMirrorAndSourceWorkQueues(t *testing.T) {
c := createJetStreamClusterExplicit(t, "WQ", 3)
defer c.shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "WQ22",
Subjects: []string{"foo"},
Replicas: 2,
Retention: nats.WorkQueuePolicy,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = js.AddStream(&nats.StreamConfig{
Name: "M",
Replicas: 2,
Mirror: &nats.StreamSource{Name: "WQ22"},
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = js.AddStream(&nats.StreamConfig{
Name: "S",
Replicas: 2,
Sources: []*nats.StreamSource{{Name: "WQ22"}},
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Allow direct sync consumers to connect.
time.Sleep(1500 * time.Millisecond)
if _, err = js.Publish("foo", []byte("ok")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
checkFor(t, 5*time.Second, 250*time.Millisecond, func() error {
if si, _ := js.StreamInfo("WQ22"); si.State.Msgs != 0 {
return fmt.Errorf("Expected no msgs for %q, got %d", "WQ22", si.State.Msgs)
}
if si, _ := js.StreamInfo("M"); si.State.Msgs != 1 {
return fmt.Errorf("Expected 1 msg for %q, got %d", "M", si.State.Msgs)
}
if si, _ := js.StreamInfo("S"); si.State.Msgs != 1 {
return fmt.Errorf("Expected 1 msg for %q, got %d", "S", si.State.Msgs)
}
return nil
})
}
func TestJetStreamClusterMirrorAndSourceInterestPolicyStream(t *testing.T) {
c := createJetStreamClusterExplicit(t, "WQ", 3)
defer c.shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "IP22",
Subjects: []string{"foo"},
Replicas: 3,
Retention: nats.InterestPolicy,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = js.AddStream(&nats.StreamConfig{
Name: "M",
Replicas: 2,
Mirror: &nats.StreamSource{Name: "IP22"},
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = js.AddStream(&nats.StreamConfig{
Name: "S",
Replicas: 2,
Sources: []*nats.StreamSource{{Name: "IP22"}},
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Allow sync consumers to connect.
time.Sleep(1500 * time.Millisecond)
if _, err = js.Publish("foo", []byte("ok")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
checkFor(t, 5*time.Second, 250*time.Millisecond, func() error {
// This one will be 0 since no other interest exists.
if si, _ := js.StreamInfo("IP22"); si.State.Msgs != 0 {
return fmt.Errorf("Expected no msgs for %q, got %d", "IP22", si.State.Msgs)
}
if si, _ := js.StreamInfo("M"); si.State.Msgs != 1 {
return fmt.Errorf("Expected 1 msg for %q, got %d", "M", si.State.Msgs)
}
if si, _ := js.StreamInfo("S"); si.State.Msgs != 1 {
return fmt.Errorf("Expected 1 msg for %q, got %d", "S", si.State.Msgs)
}
return nil
})
// Now create other interest on IP22.
sub, err := js.SubscribeSync("foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer sub.Unsubscribe()
// Allow consumer state to propagate.
time.Sleep(500 * time.Millisecond)
if _, err = js.Publish("foo", []byte("ok")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
checkFor(t, 5*time.Second, 250*time.Millisecond, func() error {
// This one will be 0 since no other interest exists.
if si, _ := js.StreamInfo("IP22"); si.State.Msgs != 1 {
return fmt.Errorf("Expected 1 msg for %q, got %d", "IP22", si.State.Msgs)
}
if si, _ := js.StreamInfo("M"); si.State.Msgs != 2 {
return fmt.Errorf("Expected 2 msgs for %q, got %d", "M", si.State.Msgs)
}
if si, _ := js.StreamInfo("S"); si.State.Msgs != 2 {
return fmt.Errorf("Expected 2 msgs for %q, got %d", "S", si.State.Msgs)
}
return nil
})
}
func TestJetStreamClusterInterestRetentionWithFilteredConsumers(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"*"}, Retention: nats.InterestPolicy, Replicas: 3})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
fsub, err := js.SubscribeSync("foo", nats.Durable("d1"))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer fsub.Unsubscribe()
bsub, err := js.SubscribeSync("bar", nats.Durable("d2"))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer bsub.Unsubscribe()
msg := []byte("FILTERED")
sendMsg := func(subj string) {
t.Helper()
if _, err = js.Publish(subj, msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
getAndAck := func(sub *nats.Subscription) {
t.Helper()
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Unexpected error getting msg: %v", err)
}
m.AckSync()
}
jsq, err := nc.JetStream(nats.MaxWait(250 * time.Millisecond))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkState := func(expected uint64) {
t.Helper()
checkFor(t, 5*time.Second, 100*time.Millisecond, func() error {
t.Helper()
si, err := jsq.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs != expected {
return fmt.Errorf("Expected %d msgs, got %d", expected, si.State.Msgs)
}
return nil
})
}
sendMsg("foo")
checkState(1)
getAndAck(fsub)
checkState(0)
sendMsg("bar")
sendMsg("foo")
checkState(2)
getAndAck(bsub)
checkState(1)
getAndAck(fsub)
checkState(0)
// Now send a bunch of messages and then delete the consumer.
for i := 0; i < 10; i++ {
sendMsg("foo")
sendMsg("bar")
}
checkState(20)
if err := js.DeleteConsumer("TEST", "d1"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if err := js.DeleteConsumer("TEST", "d2"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkState(0)
// Now make sure pull based consumers work same.
if _, err := js.PullSubscribe("foo", "dlc"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Now send a bunch of messages and then delete the consumer.
for i := 0; i < 10; i++ {
sendMsg("foo")
sendMsg("bar")
}
checkState(10)
if err := js.DeleteConsumer("TEST", "dlc"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkState(0)
}
func TestJetStreamClusterEphemeralConsumerNoImmediateInterest(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 3})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// We want to relax the strict interest requirement.
ci, err := js.AddConsumer("TEST", &nats.ConsumerConfig{DeliverSubject: "r"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
cl := c.consumerLeader("$G", "TEST", ci.Name)
mset, err := cl.GlobalAccount().lookupStream("TEST")
if err != nil {
t.Fatalf("Expected to find a stream for %q", "TEST")
}
o := mset.lookupConsumer(ci.Name)
if o == nil {
t.Fatalf("Error looking up consumer %q", ci.Name)
}
o.setInActiveDeleteThreshold(500 * time.Millisecond)
// Make sure the consumer goes away though eventually.
// Should be 5 seconds wait.
checkFor(t, 5*time.Second, 100*time.Millisecond, func() error {
if _, err := js.ConsumerInfo("TEST", ci.Name); err != nil {
return nil
}
return fmt.Errorf("Consumer still present")
})
}
func TestJetStreamClusterEphemeralConsumerCleanup(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "foo", Replicas: 2})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sub, err := js.Subscribe("foo", func(m *nats.Msg) {})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ci, _ := sub.ConsumerInfo()
if ci == nil {
t.Fatalf("Unexpected error: no consumer info")
}
// We will look up by hand this consumer to set inactive threshold lower for this test.
cl := c.consumerLeader("$G", "foo", ci.Name)
if cl == nil {
t.Fatalf("Could not find consumer leader")
}
mset, err := cl.GlobalAccount().lookupStream("foo")
if err != nil {
t.Fatalf("Expected to find a stream for %q", "foo")
}
o := mset.lookupConsumer(ci.Name)
if o == nil {
t.Fatalf("Error looking up consumer %q", ci.Name)
}
o.setInActiveDeleteThreshold(10 * time.Millisecond)
msg, toSend := []byte("Hello JS Clustering"), 10
for i := 0; i < toSend; i++ {
if _, err = js.Publish("foo", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
getConsumers := func() []string {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
var names []string
for name := range js.ConsumerNames("foo", nats.Context(ctx)) {
names = append(names, name)
}
return names
}
checkConsumer := func(expected int) {
consumers := getConsumers()
if len(consumers) != expected {
t.Fatalf("Expected %d consumers but got %d", expected, len(consumers))
}
}
checkConsumer(1)
// Now Unsubscribe, since this is ephemeral this will make this go away.
sub.Unsubscribe()
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
if consumers := getConsumers(); len(consumers) == 0 {
return nil
} else {
return fmt.Errorf("Still %d consumers remaining", len(consumers))
}
})
}
func TestJetStreamClusterEphemeralConsumersNotReplicated(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "foo", Replicas: 3})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sub, err := js.SubscribeSync("foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ci, _ := sub.ConsumerInfo()
if ci == nil {
t.Fatalf("Unexpected error: no consumer info")
}
if _, err = js.Publish("foo", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
checkSubsPending(t, sub, 1)
sub.NextMsg(0)
if ci.Cluster == nil || len(ci.Cluster.Replicas) != 0 {
t.Fatalf("Expected ephemeral to be R=1, got %+v", ci.Cluster)
}
scl := c.serverByName(ci.Cluster.Leader)
if scl == nil {
t.Fatalf("Could not select server where ephemeral consumer is running")
}
// Test migrations. If we are also metadata leader will not work so skip.
if scl == c.leader() {
return
}
scl.Shutdown()
c.waitOnStreamLeader("$G", "foo")
if _, err = js.Publish("foo", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
if _, err := sub.NextMsg(500 * time.Millisecond); err != nil {
t.Logf("Expected to see another message, but behavior is optimistic so can fail")
}
}
func TestJetStreamClusterUserSnapshotAndRestore(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 2,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
toSend := 200
for i := 0; i < toSend; i++ {
if _, err = js.Publish("foo", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
// Create consumer with no state.
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "rip", AckPolicy: nats.AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Create another consumer as well and give it a non-simplistic state.
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "dlc", AckPolicy: nats.AckExplicitPolicy, AckWait: 10 * time.Second})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
jsub, err := js.PullSubscribe("foo", "dlc")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Ack first 50.
for _, m := range fetchMsgs(t, jsub, 50, 5*time.Second) {
require_NoError(t, m.AckSync())
}
// Now ack every third message for next 50.
for i, m := range fetchMsgs(t, jsub, 50, 5*time.Second) {
if i%3 == 0 {
require_NoError(t, m.AckSync())
}
}
// Snapshot consumer info.
ci, err := jsub.ConsumerInfo()
if err != nil {
t.Fatalf("Unexpected error getting consumer info: %v", err)
}
sreq := &JSApiStreamSnapshotRequest{
DeliverSubject: nats.NewInbox(),
ChunkSize: 512,
}
req, _ := json.Marshal(sreq)
rmsg, err := nc.Request(fmt.Sprintf(JSApiStreamSnapshotT, "TEST"), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error on snapshot request: %v", err)
}
var resp JSApiStreamSnapshotResponse
json.Unmarshal(rmsg.Data, &resp)
if resp.Error != nil {
t.Fatalf("Did not get correct error response: %+v", resp.Error)
}
// Grab state for comparison.
state := *resp.State
config := *resp.Config
var snapshot []byte
done := make(chan bool)
sub, _ := nc.Subscribe(sreq.DeliverSubject, func(m *nats.Msg) {
// EOF
if len(m.Data) == 0 {
done <- true
return
}
// Could be writing to a file here too.
snapshot = append(snapshot, m.Data...)
// Flow ack
m.Respond(nil)
})
defer sub.Unsubscribe()
// Wait to receive the snapshot.
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive our snapshot in time")
}
var rresp JSApiStreamRestoreResponse
rreq := &JSApiStreamRestoreRequest{
Config: config,
State: state,
}
req, _ = json.Marshal(rreq)
// Make sure a restore to an existing stream fails.
rmsg, err = nc.Request(fmt.Sprintf(JSApiStreamRestoreT, "TEST"), req, 2*time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
json.Unmarshal(rmsg.Data, &rresp)
if !IsNatsErr(rresp.Error, JSStreamNameExistRestoreFailedErr) {
t.Fatalf("Did not get correct error response: %+v", rresp.Error)
}
if _, err := js.StreamInfo("TEST"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Now make sure a restore will work.
// Delete our stream first.
if err := js.DeleteStream("TEST"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.StreamInfo("TEST"); err == nil || !strings.Contains(err.Error(), "not found") {
t.Fatalf("Expected not found error: %v", err)
}
// This should work properly.
rmsg, err = nc.Request(fmt.Sprintf(JSApiStreamRestoreT, "TEST"), req, 5*time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
rresp.Error = nil
json.Unmarshal(rmsg.Data, &rresp)
if rresp.Error != nil {
t.Fatalf("Got an unexpected error response: %+v", rresp.Error)
}
if rresp.DeliverSubject == _EMPTY_ {
t.Fatalf("No deliver subject set on response: %+v", rresp)
}
// Send our snapshot back in to restore the stream.
// Can be any size message.
var chunk [1024]byte
for r := bytes.NewReader(snapshot); ; {
n, err := r.Read(chunk[:])
if err != nil {
break
}
nc.Request(rresp.DeliverSubject, chunk[:n], time.Second)
}
rmsg, err = nc.Request(rresp.DeliverSubject, nil, 2*time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
rresp.Error = nil
json.Unmarshal(rmsg.Data, &rresp)
if rresp.Error != nil {
t.Fatalf("Got an unexpected error response: %+v", rresp.Error)
}
si, err := js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si == nil || si.Config.Name != "TEST" || si.State.Msgs != uint64(toSend) {
t.Fatalf("StreamInfo is not correct %+v", si)
}
// Make sure the replicas become current eventually. They will be doing catchup.
checkFor(t, 10*time.Second, 100*time.Millisecond, func() error {
return checkState(t, c, "$G", "TEST")
})
// Wait on the system to elect a leader for the restored consumer.
c.waitOnConsumerLeader("$G", "TEST", "dlc")
// Now check for the consumer being recreated.
nci, err := js.ConsumerInfo("TEST", "dlc")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// nil out timestamp for better comparison
nci.Delivered.Last, ci.Delivered.Last = nil, nil
if nci.Delivered != ci.Delivered {
t.Fatalf("Delivered states do not match %+v vs %+v", nci.Delivered, ci.Delivered)
}
nci.AckFloor.Last, ci.AckFloor.Last = nil, nil
if nci.AckFloor != ci.AckFloor {
t.Fatalf("Ack floors did not match %+v vs %+v", nci.AckFloor, ci.AckFloor)
}
// Make sure consumer works.
// It should pick up with the next delivery spot, so check for that as first message.
// We should have all the messages for first delivery delivered.
wantSeq := 101
for _, m := range fetchMsgs(t, jsub, 100, 5*time.Second) {
meta, err := m.Metadata()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if meta.Sequence.Stream != uint64(wantSeq) {
t.Fatalf("Expected stream sequence of %d, but got %d", wantSeq, meta.Sequence.Stream)
}
require_NoError(t, m.AckSync())
wantSeq++
}
// Check that redelivered come in now..
redelivered := 50/3 + 1
fetchMsgs(t, jsub, redelivered, 15*time.Second)
// Now make sure the other server was properly caughtup.
// Need to call this by hand for now.
rmsg, err = nc.Request(fmt.Sprintf(JSApiStreamLeaderStepDownT, "TEST"), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var sdResp JSApiStreamLeaderStepDownResponse
if err := json.Unmarshal(rmsg.Data, &sdResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if sdResp.Error != nil {
t.Fatalf("Unexpected error: %+v", sdResp.Error)
}
c.waitOnStreamLeader("$G", "TEST")
si, err = js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %+v", err)
}
if si.State.Msgs != uint64(toSend) {
t.Fatalf("Unexpected stream info: %+v", si)
}
// Check idle consumer
c.waitOnConsumerLeader("$G", "TEST", "rip")
// Now check for the consumer being recreated.
if _, err := js.ConsumerInfo("TEST", "rip"); err != nil {
t.Fatalf("Unexpected error: %+v", err)
}
}
func TestJetStreamClusterUserSnapshotAndRestoreConfigChanges(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
// FIXME(dlc) - Do case with R=1
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 2,
}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
toSend := 10
for i := 0; i < toSend; i++ {
if _, err := js.Publish("foo", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
getSnapshot := func() ([]byte, *StreamState) {
t.Helper()
sreq := &JSApiStreamSnapshotRequest{
DeliverSubject: nats.NewInbox(),
ChunkSize: 1024,
}
req, _ := json.Marshal(sreq)
rmsg, err := nc.Request(fmt.Sprintf(JSApiStreamSnapshotT, "TEST"), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error on snapshot request: %v", err)
}
var resp JSApiStreamSnapshotResponse
json.Unmarshal(rmsg.Data, &resp)
if resp.Error != nil {
t.Fatalf("Did not get correct error response: %+v", resp.Error)
}
var snapshot []byte
done := make(chan bool)
sub, _ := nc.Subscribe(sreq.DeliverSubject, func(m *nats.Msg) {
// EOF
if len(m.Data) == 0 {
done <- true
return
}
// Could be writing to a file here too.
snapshot = append(snapshot, m.Data...)
// Flow ack
m.Respond(nil)
})
defer sub.Unsubscribe()
// Wait to receive the snapshot.
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive our snapshot in time")
}
return snapshot, resp.State
}
restore := func(cfg *StreamConfig, state *StreamState, snap []byte) *nats.StreamInfo {
rreq := &JSApiStreamRestoreRequest{
Config: *cfg,
State: *state,
}
req, err := json.Marshal(rreq)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
rmsg, err := nc.Request(fmt.Sprintf(JSApiStreamRestoreT, cfg.Name), req, 5*time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var rresp JSApiStreamRestoreResponse
json.Unmarshal(rmsg.Data, &rresp)
if rresp.Error != nil {
t.Fatalf("Got an unexpected error response: %+v", rresp.Error)
}
if rresp.DeliverSubject == _EMPTY_ {
t.Fatalf("No deliver subject set on response: %+v", rresp)
}
// Send our snapshot back in to restore the stream.
// Can be any size message.
var chunk [1024]byte
for r := bytes.NewReader(snap); ; {
n, err := r.Read(chunk[:])
if err != nil {
break
}
nc.Request(rresp.DeliverSubject, chunk[:n], time.Second)
}
rmsg, err = nc.Request(rresp.DeliverSubject, nil, 2*time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
rresp.Error = nil
json.Unmarshal(rmsg.Data, &rresp)
if rresp.Error != nil {
t.Fatalf("Got an unexpected error response: %+v", rresp.Error)
}
si, err := js.StreamInfo(cfg.Name)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
return si
}
snap, state := getSnapshot()
if err := js.DeleteStream("TEST"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Now change subjects.
ncfg := &StreamConfig{
Name: "TEST",
Subjects: []string{"bar", "baz"},
Storage: FileStorage,
Replicas: 2,
}
if si := restore(ncfg, state, snap); !reflect.DeepEqual(si.Config.Subjects, ncfg.Subjects) {
t.Fatalf("Did not get expected stream info: %+v", si)
}
if err := js.DeleteStream("TEST"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Storage
ncfg.Storage = MemoryStorage
if si := restore(ncfg, state, snap); !reflect.DeepEqual(si.Config.Subjects, ncfg.Subjects) {
t.Fatalf("Did not get expected stream info: %+v", si)
}
if err := js.DeleteStream("TEST"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Now replicas
ncfg.Replicas = 3
if si := restore(ncfg, state, snap); !reflect.DeepEqual(si.Config.Subjects, ncfg.Subjects) {
t.Fatalf("Did not get expected stream info: %+v", si)
}
}
func TestJetStreamClusterAccountInfoAndLimits(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R5S", 5)
defer c.shutdown()
// Adjust our limits.
c.updateLimits("$G", map[string]JetStreamAccountLimits{
_EMPTY_: {
MaxMemory: 1024,
MaxStore: 8000,
MaxStreams: 3,
MaxConsumers: 2,
},
})
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "foo", Replicas: 1}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.AddStream(&nats.StreamConfig{Name: "bar", Replicas: 2}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.AddStream(&nats.StreamConfig{Name: "baz", Replicas: 3}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Create with same config is idempotent, and must not exceed max streams as it already exists.
if _, err := js.AddStream(&nats.StreamConfig{Name: "baz", Replicas: 3}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sendBatch := func(subject string, n int) {
t.Helper()
for i := 0; i < n; i++ {
if _, err := js.Publish(subject, []byte("JSC-OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
}
sendBatch("foo", 25)
sendBatch("bar", 75)
sendBatch("baz", 10)
accountStats := func() *nats.AccountInfo {
t.Helper()
info, err := js.AccountInfo()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
return info
}
// If subject is not 3 letters or payload not 2 this needs to change.
const msgSize = uint64(22 + 3 + 6 + 8)
stats := accountStats()
if stats.Streams != 3 {
t.Fatalf("Should have been tracking 3 streams, found %d", stats.Streams)
}
expectedSize := 25*msgSize + 75*msgSize*2 + 10*msgSize*3
// This may lag.
checkFor(t, 5*time.Second, 500*time.Millisecond, func() error {
if stats.Store != expectedSize {
err := fmt.Errorf("Expected store size to be %d, got %+v\n", expectedSize, stats)
stats = accountStats()
return err
}
return nil
})
// Check limit enforcement.
if _, err := js.AddStream(&nats.StreamConfig{Name: "fail", Replicas: 3}); err == nil {
t.Fatalf("Expected an error but got none")
}
// We should be at 7995 at the moment with a limit of 8000, so any message will go over.
if _, err := js.Publish("baz", []byte("JSC-NOT-OK")); err == nil {
t.Fatalf("Expected publish error but got none")
}
// Check consumers
_, err := js.AddConsumer("foo", &nats.ConsumerConfig{Durable: "dlc", AckPolicy: nats.AckExplicitPolicy})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Create (with explicit create API) for the same consumer must be idempotent, and not trigger limit.
obsReq := CreateConsumerRequest{
Stream: "foo",
Config: ConsumerConfig{Durable: "bar"},
Action: ActionCreate,
}
req, err := json.Marshal(obsReq)
require_NoError(t, err)
msg, err := nc.Request(fmt.Sprintf(JSApiDurableCreateT, "foo", "bar"), req, time.Second)
require_NoError(t, err)
var resp JSApiConsumerInfoResponse
require_NoError(t, json.Unmarshal(msg.Data, &resp))
if resp.Error != nil {
t.Fatalf("Unexpected error: %v", resp.Error)
}
msg, err = nc.Request(fmt.Sprintf(JSApiDurableCreateT, "foo", "bar"), req, time.Second)
require_NoError(t, err)
var resp2 JSApiConsumerInfoResponse
require_NoError(t, json.Unmarshal(msg.Data, &resp2))
if resp2.Error != nil {
t.Fatalf("Unexpected error: %v", resp2.Error)
}
// This should fail.
_, err = js.AddConsumer("foo", &nats.ConsumerConfig{Durable: "dlc22", AckPolicy: nats.AckExplicitPolicy})
if err == nil {
t.Fatalf("Expected error but got none")
}
}
func TestJetStreamClusterMaxStreamsReached(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomNonLeader())
defer nc.Close()
// Adjust our limits.
c.updateLimits("$G", map[string]JetStreamAccountLimits{
_EMPTY_: {
MaxMemory: 1024,
MaxStore: 1024,
MaxStreams: 1,
},
})
// Many stream creations in parallel for the same stream should not result in
// maximum number of streams reached error. All should have a successful response.
var wg sync.WaitGroup
for i := 0; i < 15; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 1,
})
require_NoError(t, err)
}()
}
wg.Wait()
require_NoError(t, js.DeleteStream("TEST"))
// Adjust our limits.
c.updateLimits("$G", map[string]JetStreamAccountLimits{
_EMPTY_: {
MaxMemory: 1024,
MaxStore: 1024,
MaxStreams: 2,
},
})
// Setup streams beforehand.
for d := 0; d < 2; d++ {
_, err := js.AddStream(&nats.StreamConfig{
Name: fmt.Sprintf("TEST-%d", d),
Subjects: []string{fmt.Sprintf("foo.%d", d)},
Replicas: 1,
})
require_NoError(t, err)
}
// Many stream creations in parallel for streams that already exist should not result in
// maximum number of streams reached error. All should have a successful response.
for i := 0; i < 15; i++ {
wg.Add(1)
d := i % 2
go func() {
defer wg.Done()
_, err := js.AddStream(&nats.StreamConfig{
Name: fmt.Sprintf("TEST-%d", d),
Subjects: []string{fmt.Sprintf("foo.%d", d)},
Replicas: 1,
})
require_NoError(t, err)
}()
}
wg.Wait()
}
func TestJetStreamClusterStreamLimits(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Check that large R will fail.
if _, err := js.AddStream(&nats.StreamConfig{Name: "foo", Replicas: 5}); err == nil {
t.Fatalf("Expected error but got none")
}
maxMsgs := 5
_, err := js.AddStream(&nats.StreamConfig{
Name: "foo",
Replicas: 3,
Retention: nats.LimitsPolicy,
Discard: DiscardNew,
MaxMsgSize: 11,
MaxMsgs: int64(maxMsgs),
MaxAge: 250 * time.Millisecond,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Large message should fail.
if _, err := js.Publish("foo", []byte("0123456789ZZZ")); err == nil {
t.Fatalf("Expected publish to fail")
}
for i := 0; i < maxMsgs; i++ {
if _, err := js.Publish("foo", []byte("JSC-OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
// These should fail.
if _, err := js.Publish("foo", []byte("JSC-OK")); err == nil {
t.Fatalf("Expected publish to fail")
}
// Make sure when space frees up we can send more.
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
si, err := js.StreamInfo("foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs != 0 {
return fmt.Errorf("Expected 0 msgs, got state: %+v", si.State)
}
return nil
})
if _, err := js.Publish("foo", []byte("ROUND2")); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
func TestJetStreamClusterStreamInterestOnlyPolicy(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "foo",
Replicas: 3,
Retention: nats.InterestPolicy,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
toSend := 10
// With no interest these should be no-ops.
for i := 0; i < toSend; i++ {
if _, err := js.Publish("foo", []byte("JSC-OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
si, err := js.StreamInfo("foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs != 0 {
t.Fatalf("Expected no messages with no interest, got %d", si.State.Msgs)
}
// Now create a consumer.
sub, err := js.SubscribeSync("foo", nats.Durable("dlc"))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for i := 0; i < toSend; i++ {
if _, err := js.Publish("foo", []byte("JSC-OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
checkSubsPending(t, sub, toSend)
si, err = js.StreamInfo("foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs != uint64(toSend) {
t.Fatalf("Expected %d messages with interest, got %d", toSend, si.State.Msgs)
}
if si.State.FirstSeq != uint64(toSend+1) {
t.Fatalf("Expected first sequence of %d, got %d", toSend+1, si.State.FirstSeq)
}
// Now delete the consumer.
sub.Unsubscribe()
// That should make it go away.
if _, err := js.ConsumerInfo("foo", "dlc"); err == nil {
t.Fatalf("Expected not found error, got none")
}
// Wait for the messages to be purged.
checkFor(t, 5*time.Second, 20*time.Millisecond, func() error {
si, err := js.StreamInfo("foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs == 0 {
return nil
}
return fmt.Errorf("Wanted 0 messages, got %d", si.State.Msgs)
})
}
func TestJetStreamClusterExtendedAccountInfo(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
sendBatch := func(subject string, n int) {
t.Helper()
for i := 0; i < n; i++ {
if _, err := js.Publish(subject, []byte("JSC-OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
}
// Add in some streams with msgs and consumers.
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST-1", Replicas: 2}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.SubscribeSync("TEST-1"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sendBatch("TEST-1", 25)
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST-2", Replicas: 2}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.SubscribeSync("TEST-2"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sendBatch("TEST-2", 50)
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST-3", Replicas: 3, Storage: nats.MemoryStorage}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.SubscribeSync("TEST-3"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sendBatch("TEST-3", 100)
// Go client will lag so use direct for now.
getAccountInfo := func() *nats.AccountInfo {
t.Helper()
info, err := js.AccountInfo()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
return info
}
checkFor(t, 5*time.Second, 250*time.Millisecond, func() error {
ai := getAccountInfo()
if ai.Streams != 3 || ai.Consumers != 3 {
return fmt.Errorf("AccountInfo not correct: %+v", ai)
}
if ai.API.Total < 7 {
return fmt.Errorf("Expected at least 7 total API calls, got %d", ai.API.Total)
}
return nil
})
// Now do a failure to make sure we track API errors.
js.StreamInfo("NO-STREAM")
js.ConsumerInfo("TEST-1", "NO-CONSUMER")
js.ConsumerInfo("TEST-2", "NO-CONSUMER")
js.ConsumerInfo("TEST-3", "NO-CONSUMER")
checkFor(t, 5*time.Second, 250*time.Millisecond, func() error {
ai := getAccountInfo()
if ai.API.Errors != 4 {
return fmt.Errorf("Expected 4 API calls to be errors, got %d", ai.API.Errors)
}
return nil
})
}
func TestJetStreamClusterPeerRemovalAPI(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R5S", 5)
defer c.shutdown()
// Client based API
ml := c.leader()
nc, err := nats.Connect(ml.ClientURL(), nats.UserInfo("admin", "s3cr3t!"))
if err != nil {
t.Fatalf("Failed to create system client: %v", err)
}
defer nc.Close()
// Expect error if unknown peer
req := &JSApiMetaServerRemoveRequest{Server: "S-9"}
jsreq, err := json.Marshal(req)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
rmsg, err := nc.Request(JSApiRemoveServer, jsreq, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var resp JSApiMetaServerRemoveResponse
if err := json.Unmarshal(rmsg.Data, &resp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if resp.Error == nil {
t.Fatalf("Expected an error, got none")
}
sub, err := nc.SubscribeSync(JSAdvisoryServerRemoved)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
rs := c.randomNonLeader()
req = &JSApiMetaServerRemoveRequest{Server: rs.Name()}
jsreq, err = json.Marshal(req)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
rmsg, err = nc.Request(JSApiRemoveServer, jsreq, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
resp.Error = nil
if err := json.Unmarshal(rmsg.Data, &resp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if resp.Error != nil {
t.Fatalf("Unexpected error: %+v", resp.Error)
}
c.waitOnLeader()
ml = c.leader()
checkSubsPending(t, sub, 1)
madv, _ := sub.NextMsg(0)
var adv JSServerRemovedAdvisory
if err := json.Unmarshal(madv.Data, &adv); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if adv.Server != rs.Name() {
t.Fatalf("Expected advisory about %s being removed, got %+v", rs.Name(), adv)
}
checkFor(t, 5*time.Second, 250*time.Millisecond, func() error {
for _, s := range ml.JetStreamClusterPeers() {
if s == rs.Name() {
return fmt.Errorf("Still in the peer list")
}
}
return nil
})
}
func TestJetStreamClusterPeerRemovalAndStreamReassignment(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R5S", 5)
defer c.shutdown()
// Client based API
s := c.randomNonLeader()
nc, js := jsClientConnect(t, s)
defer nc.Close()
si, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 3,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Admin based API
ml := c.leader()
nc, err = nats.Connect(ml.ClientURL(), nats.UserInfo("admin", "s3cr3t!"))
if err != nil {
t.Fatalf("Failed to create system client: %v", err)
}
defer nc.Close()
// Select the non-leader server for the stream to remove.
if len(si.Cluster.Replicas) < 2 {
t.Fatalf("Not enough replicas found: %+v", si.Cluster)
}
toRemove, cl := si.Cluster.Replicas[0].Name, c.leader()
if toRemove == cl.Name() {
toRemove = si.Cluster.Replicas[1].Name
}
req := &JSApiMetaServerRemoveRequest{Server: toRemove}
jsreq, err := json.Marshal(req)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
rmsg, err := nc.Request(JSApiRemoveServer, jsreq, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var resp JSApiMetaServerRemoveResponse
if err := json.Unmarshal(rmsg.Data, &resp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if resp.Error != nil {
t.Fatalf("Unexpected error: %+v", resp.Error)
}
// In case that server was also meta-leader.
c.waitOnLeader()
checkFor(t, 10*time.Second, 250*time.Millisecond, func() error {
for _, s := range ml.JetStreamClusterPeers() {
if s == toRemove {
return fmt.Errorf("Server still in the peer list")
}
}
return nil
})
// Now wait until the stream is now current.
checkFor(t, 10*time.Second, 100*time.Millisecond, func() error {
si, err := js.StreamInfo("TEST", nats.MaxWait(time.Second))
if err != nil {
return fmt.Errorf("Could not fetch stream info: %v", err)
}
// We should not see the old server at all.
for _, p := range si.Cluster.Replicas {
if p.Name == toRemove {
t.Fatalf("Peer not removed yet: %+v", toRemove)
}
if !p.Current {
return fmt.Errorf("Expected replica to be current: %+v", p)
}
}
if len(si.Cluster.Replicas) != 2 {
return fmt.Errorf("Expected 2 replicas, got %d", len(si.Cluster.Replicas))
}
return nil
})
}
func TestJetStreamClusterPeerRemovalAndStreamReassignmentWithoutSpace(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomNonLeader()
nc, js := jsClientConnect(t, s)
defer nc.Close()
si, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 3,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Admin based API
ml := c.leader()
nc, err = nats.Connect(ml.ClientURL(), nats.UserInfo("admin", "s3cr3t!"))
if err != nil {
t.Fatalf("Failed to create system client: %v", err)
}
defer nc.Close()
// Select the non-leader server for the stream to remove.
if len(si.Cluster.Replicas) < 2 {
t.Fatalf("Not enough replicas found: %+v", si.Cluster)
}
toRemove, cl := si.Cluster.Replicas[0].Name, c.leader()
if toRemove == cl.Name() {
toRemove = si.Cluster.Replicas[1].Name
}
req := &JSApiMetaServerRemoveRequest{Server: toRemove}
jsreq, err := json.Marshal(req)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
rmsg, err := nc.Request(JSApiRemoveServer, jsreq, 2*time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var resp JSApiMetaServerRemoveResponse
if err := json.Unmarshal(rmsg.Data, &resp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if resp.Error != nil {
t.Fatalf("Unexpected error: %+v", resp.Error)
}
checkFor(t, 10*time.Second, 250*time.Millisecond, func() error {
for _, s := range ml.JetStreamClusterPeers() {
if s == toRemove {
return fmt.Errorf("Server still in the peer list")
}
}
return nil
})
// Make sure only 2 peers at this point.
c.waitOnPeerCount(2)
// Now wait until the stream is now current.
streamCurrent := func(nr int) {
checkFor(t, 10*time.Second, 100*time.Millisecond, func() error {
si, err := js.StreamInfo("TEST", nats.MaxWait(time.Second))
if err != nil {
return fmt.Errorf("Could not fetch stream info: %v", err)
}
// We should not see the old server at all.
for _, p := range si.Cluster.Replicas {
if p.Name == toRemove {
return fmt.Errorf("Peer not removed yet: %+v", toRemove)
}
if !p.Current {
return fmt.Errorf("Expected replica to be current: %+v", p)
}
}
if len(si.Cluster.Replicas) != nr {
return fmt.Errorf("Expected %d replicas, got %d", nr, len(si.Cluster.Replicas))
}
return nil
})
}
// Make sure the peer was removed from the stream and that we did not fill the new spot.
streamCurrent(1)
// Now add in a new server and make sure it gets added to our stream.
c.addInNewServer()
c.waitOnPeerCount(3)
streamCurrent(2)
}
func TestJetStreamClusterPeerRemovalAndServerBroughtBack(t *testing.T) {
// Speed up for this test
peerRemoveTimeout = 2 * time.Second
defer func() {
peerRemoveTimeout = peerRemoveTimeoutDefault
}()
c := createJetStreamClusterExplicit(t, "R5S", 5)
defer c.shutdown()
// Client based API
ml := c.leader()
nc, err := nats.Connect(ml.ClientURL(), nats.UserInfo("admin", "s3cr3t!"))
if err != nil {
t.Fatalf("Failed to create system client: %v", err)
}
defer nc.Close()
getPeersCount := func() int {
js := ml.getJetStream()
if js == nil {
return 0
}
js.mu.RLock()
defer js.mu.RUnlock()
cc := js.cluster
if !cc.isLeader() || cc.meta == nil {
return 0
}
return len(cc.meta.Peers())
}
checkFor(t, 2*time.Second, 250*time.Millisecond, func() error {
if l := getPeersCount(); l != 5 {
return fmt.Errorf("expected 5 peers, got %d", l)
}
return nil
})
// Shutdown server first.
rs := c.randomNonLeader()
rs.Shutdown()
// Peers should still remain the same, even if one server is shut down.
checkFor(t, 2*time.Second, 250*time.Millisecond, func() error {
if l := getPeersCount(); l != 5 {
return fmt.Errorf("expected 5 peers, got %d", l)
}
return nil
})
// Peer-remove after shutdown.
req := &JSApiMetaServerRemoveRequest{Server: rs.Name()}
jsreq, err := json.Marshal(req)
require_NoError(t, err)
rmsg, err := nc.Request(JSApiRemoveServer, jsreq, time.Second)
require_NoError(t, err)
var resp JSApiMetaServerRemoveResponse
require_NoError(t, json.Unmarshal(rmsg.Data, &resp))
if resp.Error != nil {
t.Fatalf("Unexpected error: %+v", resp.Error)
}
// Peer should be removed.
checkFor(t, 2*time.Second, 250*time.Millisecond, func() error {
if l := getPeersCount(); l != 4 {
return fmt.Errorf("expected 4 peers, got %d", l)
}
return nil
})
// Bringing back the server should re-add to peers after peer-remove timeout.
c.restartServer(rs)
checkFor(t, 5*time.Second, 250*time.Millisecond, func() error {
if l := getPeersCount(); l != 5 {
return fmt.Errorf("expected 5 peers, got %d", l)
}
return nil
})
}
func TestJetStreamClusterPeerExclusionTag(t *testing.T) {
c := createJetStreamClusterWithTemplateAndModHook(t, jsClusterTempl, "C", 3,
func(serverName, clusterName, storeDir, conf string) string {
switch serverName {
case "S-1":
return fmt.Sprintf("%s\nserver_tags: [server:%s, intersect, %s]", conf, serverName, jsExcludePlacement)
case "S-2":
return fmt.Sprintf("%s\nserver_tags: [server:%s, intersect]", conf, serverName)
default:
return fmt.Sprintf("%s\nserver_tags: [server:%s]", conf, serverName)
}
})
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
for i, c := range []nats.StreamConfig{
{Replicas: 1, Placement: &nats.Placement{Tags: []string{"server:S-1"}}},
{Replicas: 2, Placement: &nats.Placement{Tags: []string{"intersect"}}},
{Replicas: 3}, // not enough server without !jetstream
} {
c.Name = fmt.Sprintf("TEST%d", i)
c.Subjects = []string{c.Name}
_, err := js.AddStream(&c)
require_Error(t, err)
require_Contains(t, err.Error(), "no suitable peers for placement", "exclude tag set")
}
// Test update failure
cfg := &nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}, Replicas: 2}
_, err := js.AddStream(cfg)
require_NoError(t, err)
cfg.Replicas = 3
_, err = js.UpdateStream(cfg)
require_Error(t, err)
require_Contains(t, err.Error(), "no suitable peers for placement", "exclude tag set")
// Test tag reload removing !jetstream tag, and allowing placement again
srv := c.serverByName("S-1")
v, err := srv.Varz(nil)
require_NoError(t, err)
require_True(t, v.Tags.Contains(jsExcludePlacement))
content, err := os.ReadFile(srv.configFile)
require_NoError(t, err)
newContent := strings.ReplaceAll(string(content), fmt.Sprintf(", %s]", jsExcludePlacement), "]")
changeCurrentConfigContentWithNewContent(t, srv.configFile, []byte(newContent))
require_NoError(t, srv.Reload())
srv.sendStatszUpdate()
v, err = srv.Varz(nil)
require_NoError(t, err)
require_True(t, !v.Tags.Contains(jsExcludePlacement))
checkFor(t, 20*time.Second, 500*time.Millisecond, func() error {
for _, s := range c.servers {
var err error
s.nodeToInfo.Range(func(sn any, value any) bool {
ni := value.(nodeInfo)
if ni.name != "S-1" {
return true
}
if ni.tags.Contains("!jetstream") {
err = fmt.Errorf("%s still has jetstream exclude tag", sn)
return false
}
return true
})
if err != nil {
return err
}
}
return nil
})
cfg.Replicas = 3
_, err = js.UpdateStream(cfg)
require_NoError(t, err)
}
func TestJetStreamClusterAccountPurge(t *testing.T) {
sysKp, syspub := createKey(t)
sysJwt := encodeClaim(t, jwt.NewAccountClaims(syspub), syspub)
sysCreds := newUser(t, sysKp)
accKp, accpub := createKey(t)
accClaim := jwt.NewAccountClaims(accpub)
accClaim.Limits.JetStreamLimits.DiskStorage = 1024 * 1024 * 5
accClaim.Limits.JetStreamLimits.MemoryStorage = 1024 * 1024 * 5
accJwt := encodeClaim(t, accClaim, accpub)
accCreds := newUser(t, accKp)
tmlp := `
listen: 127.0.0.1:-1
server_name: %s
jetstream: {max_mem_store: 256MB, max_file_store: 2GB, store_dir: '%s'}
leaf {
listen: 127.0.0.1:-1
}
cluster {
name: %s
listen: 127.0.0.1:%d
routes = [%s]
}
`
c := createJetStreamClusterWithTemplateAndModHook(t, tmlp, "cluster", 3,
func(serverName, clustername, storeDir, conf string) string {
return conf + fmt.Sprintf(`
operator: %s
system_account: %s
resolver: {
type: full
dir: '%s/jwt'
timeout: "10ms"
}`, ojwt, syspub, storeDir)
})
defer c.shutdown()
c.waitOnLeader()
updateJwt(t, c.randomServer().ClientURL(), sysCreds, sysJwt, 3)
updateJwt(t, c.randomServer().ClientURL(), sysCreds, accJwt, 3)
c.waitOnAccount(accpub)
createTestData := func(t *testing.T) {
nc, js := jsClientConnect(t, c.randomNonLeader(), nats.UserCredentials(accCreds))
defer nc.Close()
// Prevent 'nats: JetStream not enabled for account' when creating the first stream.
c.waitOnAccount(accpub)
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST1",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
c.waitOnStreamLeader(accpub, "TEST1")
ci, err := js.AddConsumer("TEST1",
&nats.ConsumerConfig{Durable: "DUR1",
AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
require_True(t, ci.Config.Replicas == 0)
ci, err = js.AddConsumer("TEST1",
&nats.ConsumerConfig{Durable: "DUR2",
AckPolicy: nats.AckExplicitPolicy,
Replicas: 1})
require_NoError(t, err)
require_True(t, ci.Config.Replicas == 1)
toSend := uint64(1_000)
for i := uint64(0); i < toSend; i++ {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST2",
Subjects: []string{"bar"},
Replicas: 1,
})
require_NoError(t, err)
ci, err = js.AddConsumer("TEST2",
&nats.ConsumerConfig{Durable: "DUR1",
AckPolicy: nats.AckExplicitPolicy,
Replicas: 0})
require_NoError(t, err)
require_True(t, ci.Config.Replicas == 0)
for i := uint64(0); i < toSend; i++ {
_, err = js.Publish("bar", nil)
require_NoError(t, err)
}
}
inspectDirs := func(t *testing.T, sysTotal, accTotal int) error {
t.Helper()
sysDirs := 0
accDirs := 0
for _, s := range c.servers {
files, err := os.ReadDir(filepath.Join(s.getOpts().StoreDir, "jetstream", syspub, "_js_"))
require_NoError(t, err)
sysDirs += len(files) - 1 // sub 1 for _meta_
files, err = os.ReadDir(filepath.Join(s.getOpts().StoreDir, "jetstream", accpub, "streams"))
if err == nil || err.(*os.PathError).Error() == "no such file or directory" {
accDirs += len(files)
}
}
if sysDirs != sysTotal || accDirs != accTotal {
return fmt.Errorf("expected directory count does not match %d == %d, %d == %d",
sysDirs, sysTotal, accDirs, accTotal)
}
return nil
}
checkForDirs := func(t *testing.T, sysTotal, accTotal int) {
t.Helper()
checkFor(t, 20*time.Second, 250*time.Millisecond, func() error {
return inspectDirs(t, sysTotal, accTotal)
})
}
purge := func(t *testing.T) {
t.Helper()
ncsys, err := nats.Connect(c.randomServer().ClientURL(), nats.UserCredentials(sysCreds))
require_NoError(t, err)
defer ncsys.Close()
request := func() error {
var resp JSApiAccountPurgeResponse
m, err := ncsys.Request(fmt.Sprintf(JSApiAccountPurgeT, accpub), nil, time.Second)
if err != nil {
return err
}
if err := json.Unmarshal(m.Data, &resp); err != nil {
return err
}
if !resp.Initiated {
return fmt.Errorf("not started")
}
return nil
}
checkFor(t, 30*time.Second, 250*time.Millisecond, request)
}
t.Run("startup-cleanup", func(t *testing.T) {
_, newCleanupAcc1 := createKey(t)
_, newCleanupAcc2 := createKey(t)
for _, s := range c.servers {
os.MkdirAll(filepath.Join(s.getOpts().StoreDir, JetStreamStoreDir, newCleanupAcc1, streamsDir), defaultDirPerms)
os.MkdirAll(filepath.Join(s.getOpts().StoreDir, JetStreamStoreDir, newCleanupAcc2), defaultDirPerms)
}
createTestData(t)
checkForDirs(t, 6, 4)
c.stopAll()
c.restartAll()
for _, s := range c.servers {
accDir := filepath.Join(s.getOpts().StoreDir, JetStreamStoreDir, newCleanupAcc1)
_, e := os.Stat(filepath.Join(accDir, streamsDir))
require_Error(t, e)
require_True(t, os.IsNotExist(e))
_, e = os.Stat(accDir)
require_Error(t, e)
require_True(t, os.IsNotExist(e))
_, e = os.Stat(filepath.Join(s.getOpts().StoreDir, JetStreamStoreDir, newCleanupAcc2))
require_Error(t, e)
require_True(t, os.IsNotExist(e))
}
checkForDirs(t, 6, 4)
// Make sure we have a leader for all assets before moving to the next test
c.waitOnStreamLeader(accpub, "TEST1")
c.waitOnConsumerLeader(accpub, "TEST1", "DUR1")
c.waitOnConsumerLeader(accpub, "TEST1", "DUR2")
c.waitOnStreamLeader(accpub, "TEST2")
c.waitOnConsumerLeader(accpub, "TEST2", "DUR1")
})
t.Run("purge-with-restart", func(t *testing.T) {
createTestData(t)
checkForDirs(t, 6, 4)
purge(t)
checkForDirs(t, 0, 0)
c.stopAll()
c.restartAll()
checkForDirs(t, 0, 0)
})
t.Run("purge-with-reuse", func(t *testing.T) {
createTestData(t)
checkForDirs(t, 6, 4)
purge(t)
checkForDirs(t, 0, 0)
createTestData(t)
checkForDirs(t, 6, 4)
purge(t)
checkForDirs(t, 0, 0)
})
t.Run("purge-deleted-account", func(t *testing.T) {
createTestData(t)
checkForDirs(t, 6, 4)
c.stopAll()
for _, s := range c.servers {
require_NoError(t, os.Remove(s.getOpts().StoreDir+"/jwt/"+accpub+".jwt"))
}
c.restartAll()
checkForDirs(t, 6, 4)
purge(t)
checkForDirs(t, 0, 0)
c.stopAll()
c.restartAll()
checkForDirs(t, 0, 0)
})
}
func TestJetStreamClusterScaleConsumer(t *testing.T) {
c := createJetStreamClusterWithTemplate(t, jsClusterTempl, "C", 3)
defer c.shutdown()
srv := c.randomNonLeader()
nc, js := jsClientConnect(t, srv)
defer nc.Close()
si, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
durCfg := &nats.ConsumerConfig{Durable: "DUR", AckPolicy: nats.AckExplicitPolicy}
ci, err := js.AddConsumer("TEST", durCfg)
require_NoError(t, err)
require_True(t, ci.Config.Replicas == 0)
toSend := uint64(1_000)
for i := uint64(0); i < toSend; i++ {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
s, err := js.PullSubscribe("foo", "DUR")
require_NoError(t, err)
consumeOne := func(expSeq uint64) error {
if ci, err := js.ConsumerInfo("TEST", "DUR"); err != nil {
return err
} else if ci.Delivered.Stream != expSeq {
return fmt.Errorf("pre: not expected delivered stream %d, got %d", expSeq, ci.Delivered.Stream)
} else if ci.Delivered.Consumer != expSeq {
return fmt.Errorf("pre: not expected delivered consumer %d, got %d", expSeq, ci.Delivered.Consumer)
} else if ci.AckFloor.Stream != expSeq {
return fmt.Errorf("pre: not expected ack stream %d, got %d", expSeq, ci.AckFloor.Stream)
} else if ci.AckFloor.Consumer != expSeq {
return fmt.Errorf("pre: not expected ack consumer %d, got %d", expSeq, ci.AckFloor.Consumer)
}
if m, err := s.Fetch(1); err != nil {
return err
} else if err := m[0].AckSync(); err != nil {
return err
}
expSeq = expSeq + 1
if ci, err := js.ConsumerInfo("TEST", "DUR"); err != nil {
return err
} else if ci.Delivered.Stream != expSeq {
return fmt.Errorf("post: not expected delivered stream %d, got %d", expSeq, ci.Delivered.Stream)
} else if ci.Delivered.Consumer != expSeq {
return fmt.Errorf("post: not expected delivered consumer %d, got %d", expSeq, ci.Delivered.Consumer)
} else if ci.AckFloor.Stream != expSeq {
return fmt.Errorf("post: not expected ack stream %d, got %d", expSeq, ci.AckFloor.Stream)
} else if ci.AckFloor.Consumer != expSeq {
return fmt.Errorf("post: not expected ack consumer %d, got %d", expSeq, ci.AckFloor.Consumer)
}
return nil
}
require_NoError(t, consumeOne(0))
// scale down, up, down and up to default == 3 again
for i, r := range []int{1, 3, 1, 0} {
durCfg.Replicas = r
if r == 0 {
r = si.Config.Replicas
}
js.UpdateConsumer("TEST", durCfg)
checkFor(t, time.Second*30, time.Millisecond*250, func() error {
if ci, err = js.ConsumerInfo("TEST", "DUR"); err != nil {
return err
} else if ci.Cluster == nil {
return errors.New("no cluster info")
} else if ci.Cluster.Leader == _EMPTY_ {
return errors.New("no leader")
} else if len(ci.Cluster.Replicas) != r-1 {
return fmt.Errorf("not enough replica, got %d wanted %d", len(ci.Cluster.Replicas), r-1)
} else {
for _, r := range ci.Cluster.Replicas {
if !r.Current || r.Offline || r.Lag != 0 {
return fmt.Errorf("replica %s not current %t offline %t lag %d", r.Name, r.Current, r.Offline, r.Lag)
}
}
}
return nil
})
require_NoError(t, consumeOne(uint64(i+1)))
}
}
func TestJetStreamClusterConsumerScaleUp(t *testing.T) {
c := createJetStreamCluster(t, jsClusterTempl, "HUB", _EMPTY_, 3, 22020, true)
defer c.shutdown()
// Client based API
srv := c.randomNonLeader()
nc, js := jsClientConnect(t, srv)
defer nc.Close()
scfg := nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 1,
}
_, err := js.AddStream(&scfg)
require_NoError(t, err)
defer js.DeleteStream("TEST")
dcfg := nats.ConsumerConfig{
Durable: "DUR",
AckPolicy: nats.AckExplicitPolicy,
Replicas: 0}
_, err = js.AddConsumer("TEST", &dcfg)
require_NoError(t, err)
for i := 0; i < 100; i++ {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
scfg.Replicas = 2
_, err = js.UpdateStream(&scfg)
require_NoError(t, err)
// The scale up issue shows itself as permanent loss of consumer leadership
// So give it some time for the change to propagate to new consumer peers and the quorum to disrupt
// 2 seconds is a value arrived by experimentally, no sleep or a sleep of 1sec always had the test pass a lot.
time.Sleep(2 * time.Second)
c.waitOnStreamLeader("$G", "TEST")
// There is also a timing component to the issue triggering.
c.waitOnConsumerLeader("$G", "TEST", "DUR")
}
func TestJetStreamClusterPeerOffline(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R5S", 5)
defer c.shutdown()
ml := c.leader()
rs := c.randomNonLeader()
checkPeer := func(ml, rs *Server, shouldBeOffline bool) {
t.Helper()
checkFor(t, 5*time.Second, 50*time.Millisecond, func() error {
var found bool
for _, s := range ml.JetStreamClusterPeers() {
if s == rs.Name() {
found = true
break
}
}
if !shouldBeOffline && !found {
return fmt.Errorf("Server %q not in the peers list", rs.Name())
} else if shouldBeOffline && found {
return fmt.Errorf("Server %q should not be in the peers list", rs.Name())
}
var ok bool
ml.nodeToInfo.Range(func(k, v any) bool {
if si := v.(nodeInfo); si.name == rs.Name() {
if shouldBeOffline && si.offline || !shouldBeOffline && !si.offline {
ok = true
return false
}
}
return true
})
if !ok {
if shouldBeOffline {
return fmt.Errorf("Server %q should be marked as online", rs.Name())
}
return fmt.Errorf("Server %q is still marked as online", rs.Name())
}
return nil
})
}
// Shutdown the server and make sure that it is now showing as offline.
rs.Shutdown()
checkPeer(ml, rs, true)
// Now restart that server and check that is no longer offline.
oldrs := rs
rs, _ = RunServerWithConfig(rs.getOpts().ConfigFile)
defer rs.Shutdown()
// Replaced old with new server
for i := 0; i < len(c.servers); i++ {
if c.servers[i] == oldrs {
c.servers[i] = rs
}
}
// Wait for cluster to be formed
checkClusterFormed(t, c.servers...)
// Make sure that we have a leader (there can always be a re-election)
c.waitOnLeader()
ml = c.leader()
// Now check that rs is not offline
checkPeer(ml, rs, false)
}
func TestJetStreamClusterNoQuorumStepdown(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Setup subscription for leader elected.
lesub, err := nc.SubscribeSync(JSAdvisoryStreamLeaderElectedPre + ".*")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.AddStream(&nats.StreamConfig{Name: "NO-Q", Replicas: 2}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Make sure we received our leader elected advisory.
leadv, _ := lesub.NextMsg(0)
if leadv == nil {
t.Fatalf("Expected to receive a leader elected advisory")
}
var le JSStreamLeaderElectedAdvisory
if err := json.Unmarshal(leadv.Data, &le); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if ln := c.streamLeader("$G", "NO-Q").Name(); le.Leader != ln {
t.Fatalf("Expected to have leader %q in elect advisory, got %q", ln, le.Leader)
}
payload := []byte("Hello JSC")
for i := 0; i < 10; i++ {
if _, err := js.Publish("NO-Q", payload); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
// Setup subscription for leader elected.
clesub, err := nc.SubscribeSync(JSAdvisoryConsumerLeaderElectedPre + ".*.*")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Make durable to have R match Stream.
sub, err := js.SubscribeSync("NO-Q", nats.Durable("rr"))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ci, err := sub.ConsumerInfo()
if err != nil || ci == nil {
t.Fatalf("Unexpected error: %v", err)
}
// Make sure we received our consumer leader elected advisory.
leadv, _ = clesub.NextMsg(0)
if leadv == nil {
t.Fatalf("Expected to receive a consumer leader elected advisory")
}
// Shutdown the non-leader.
c.randomNonStreamLeader("$G", "NO-Q").Shutdown()
// This should eventually have us stepdown as leader since we would have lost quorum with R=2.
checkFor(t, 5*time.Second, 500*time.Millisecond, func() error {
if sl := c.streamLeader("$G", "NO-Q"); sl == nil {
return nil
}
return fmt.Errorf("Still have leader for stream")
})
notAvailableErr := func(err error) bool {
return err != nil && (strings.Contains(err.Error(), "unavailable") || err == context.DeadlineExceeded)
}
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
if cl := c.consumerLeader("$G", "NO-Q", ci.Name); cl == nil {
return nil
}
return fmt.Errorf("Still have leader for consumer")
})
if _, err = js.ConsumerInfo("NO-Q", ci.Name); !notAvailableErr(err) {
t.Fatalf("Expected an 'unavailable' error, got %v", err)
}
if _, err := sub.ConsumerInfo(); !notAvailableErr(err) {
t.Fatalf("Expected an 'unavailable' error, got %v", err)
}
// Now let's take out the other non meta-leader
// We should get same error for general API calls.
c.randomNonLeader().Shutdown()
c.expectNoLeader()
// Now make sure the general JS API responds with system unavailable.
if _, err = js.AccountInfo(); !notAvailableErr(err) {
t.Fatalf("Expected an 'unavailable' error, got %v", err)
}
if _, err := js.AddStream(&nats.StreamConfig{Name: "NO-Q33", Replicas: 2}); !notAvailableErr(err) {
t.Fatalf("Expected an 'unavailable' error, got %v", err)
}
if _, err := js.UpdateStream(&nats.StreamConfig{Name: "NO-Q33", Replicas: 2}); !notAvailableErr(err) {
t.Fatalf("Expected an 'unavailable' error, got %v", err)
}
if err := js.DeleteStream("NO-Q"); !notAvailableErr(err) {
t.Fatalf("Expected an 'unavailable' error, got %v", err)
}
if err := js.PurgeStream("NO-Q"); !notAvailableErr(err) {
t.Fatalf("Expected an 'unavailable' error, got %v", err)
}
if err := js.DeleteMsg("NO-Q", 1); !notAvailableErr(err) {
t.Fatalf("Expected an 'unavailable' error, got %v", err)
}
// Consumer
if _, err := js.AddConsumer("NO-Q", &nats.ConsumerConfig{Durable: "dlc", AckPolicy: nats.AckExplicitPolicy}); !notAvailableErr(err) {
t.Fatalf("Expected an 'unavailable' error, got %v", err)
}
if err := js.DeleteConsumer("NO-Q", "dlc"); !notAvailableErr(err) {
t.Fatalf("Expected an 'unavailable' error, got %v", err)
}
if _, err := js.ConsumerInfo("NO-Q", "dlc"); !notAvailableErr(err) {
t.Fatalf("Expected an 'unavailable' error, got %v", err)
}
// Listers
for info := range js.StreamsInfo() {
t.Fatalf("Unexpected stream info, got %v", info)
}
for info := range js.ConsumersInfo("NO-Q") {
t.Fatalf("Unexpected consumer info, got %v", info)
}
}
func TestJetStreamClusterCreateResponseAdvisoriesHaveSubject(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
sub, err := nc.SubscribeSync("$JS.EVENT.ADVISORY.API")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer sub.Unsubscribe()
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 2}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.SubscribeSync("TEST", nats.Durable("DLC")); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if err := js.PurgeStream("TEST"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if err := js.DeleteStream("TEST"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkSubsPending(t, sub, 6)
for m, err := sub.NextMsg(0); err == nil; m, err = sub.NextMsg(0) {
var audit JSAPIAudit
if err := json.Unmarshal(m.Data, &audit); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if audit.Subject == _EMPTY_ {
t.Fatalf("Expected subject, got nothing")
}
}
}
func TestJetStreamClusterRestartAndRemoveAdvisories(t *testing.T) {
// FIXME(dlc) - Flaky on Travis, skip for now.
skip(t)
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
sub, err := nc.SubscribeSync("$JS.EVENT.ADVISORY.API")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer sub.Unsubscribe()
csub, err := nc.SubscribeSync("$JS.EVENT.ADVISORY.*.CREATED.>")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer csub.Unsubscribe()
nc.Flush()
sendBatch := func(subject string, n int) {
t.Helper()
for i := 0; i < n; i++ {
if _, err := js.Publish(subject, []byte("JSC-OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
}
// Add in some streams with msgs and consumers.
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST-1", Replicas: 2}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.SubscribeSync("TEST-1", nats.Durable("DC")); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sendBatch("TEST-1", 25)
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST-2", Replicas: 2}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.SubscribeSync("TEST-2", nats.Durable("DC")); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sendBatch("TEST-2", 50)
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST-3", Replicas: 3, Storage: nats.MemoryStorage}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.SubscribeSync("TEST-3", nats.Durable("DC")); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sendBatch("TEST-3", 100)
drainSub := func(sub *nats.Subscription) {
for _, err := sub.NextMsg(0); err == nil; _, err = sub.NextMsg(0) {
}
}
// Wait for the advisories for all streams and consumers.
checkSubsPending(t, sub, 12) // 3 streams, 3*2 consumers, 3 stream names lookups for creating consumers.
drainSub(sub)
// Created audit events.
checkSubsPending(t, csub, 6)
drainSub(csub)
usub, err := nc.SubscribeSync("$JS.EVENT.ADVISORY.*.UPDATED.>")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer usub.Unsubscribe()
nc.Flush()
checkSubsPending(t, csub, 0)
checkSubsPending(t, sub, 0)
checkSubsPending(t, usub, 0)
// Now restart the other two servers we are not connected to.
for _, cs := range c.servers {
if cs != s {
cs.Shutdown()
c.restartServer(cs)
}
}
c.waitOnAllCurrent()
checkSubsPending(t, csub, 0)
checkSubsPending(t, sub, 0)
checkSubsPending(t, usub, 0)
dsub, err := nc.SubscribeSync("$JS.EVENT.ADVISORY.*.DELETED.>")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer dsub.Unsubscribe()
nc.Flush()
c.waitOnConsumerLeader("$G", "TEST-1", "DC")
c.waitOnLeader()
// Now check delete advisories as well.
if err := js.DeleteConsumer("TEST-1", "DC"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkSubsPending(t, csub, 0)
checkSubsPending(t, dsub, 1)
checkSubsPending(t, sub, 1)
checkSubsPending(t, usub, 0)
drainSub(dsub)
if err := js.DeleteStream("TEST-3"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkSubsPending(t, dsub, 2) // Stream and the consumer underneath.
checkSubsPending(t, sub, 2)
}
func TestJetStreamClusterNoDuplicateOnNodeRestart(t *testing.T) {
c := createJetStreamClusterExplicit(t, "ND", 2)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sl := c.streamLeader("$G", "TEST")
if s == sl {
nc.Close()
nc, js = jsClientConnect(t, s)
defer nc.Close()
}
sub, err := js.SubscribeSync("foo", nats.Durable("dlc"))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js.Publish("foo", []byte("msg1"))
if m, err := sub.NextMsg(time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
} else {
m.AckSync()
}
sl.Shutdown()
c.restartServer(sl)
c.waitOnStreamLeader("$G", "TEST")
c.waitOnConsumerLeader("$G", "TEST", "dlc")
// Send second msg
js.Publish("foo", []byte("msg2"))
msg, err := sub.NextMsg(5 * time.Second)
if err != nil {
t.Fatalf("Error getting message: %v", err)
}
if string(msg.Data) != "msg2" {
t.Fatalf("Unexpected message: %s", msg.Data)
}
msg.AckSync()
// Make sure we don't get a duplicate.
msg, err = sub.NextMsg(250 * time.Millisecond)
if err == nil {
t.Fatalf("Should have gotten an error, got %s", msg.Data)
}
}
func TestJetStreamClusterNoDupePeerSelection(t *testing.T) {
c := createJetStreamClusterExplicit(t, "NDP", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Create 10 streams. Make sure none of them have a replica
// that is the same as the leader.
for i := 1; i <= 10; i++ {
si, err := js.AddStream(&nats.StreamConfig{
Name: fmt.Sprintf("TEST-%d", i),
Replicas: 3,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.Cluster == nil || si.Cluster.Leader == "" || len(si.Cluster.Replicas) != 2 {
t.Fatalf("Unexpected cluster state for stream info: %+v\n", si.Cluster)
}
// Make sure that the replicas are not same as the leader.
for _, pi := range si.Cluster.Replicas {
if pi.Name == si.Cluster.Leader {
t.Fatalf("Found replica that is same as leader, meaning 2 nodes placed on same server")
}
}
// Now do a consumer and check same thing.
sub, err := js.SubscribeSync(si.Config.Name)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ci, err := sub.ConsumerInfo()
if err != nil {
t.Fatalf("Unexpected error getting consumer info: %v", err)
}
for _, pi := range ci.Cluster.Replicas {
if pi.Name == ci.Cluster.Leader {
t.Fatalf("Found replica that is same as leader, meaning 2 nodes placed on same server")
}
}
}
}
func TestJetStreamClusterStreamRemovePeer(t *testing.T) {
c := createJetStreamClusterExplicit(t, "RNS", 5)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 3})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Send in 10 messages.
msg, toSend := []byte("Hello JS Clustering"), 10
for i := 0; i < toSend; i++ {
if _, err = js.Publish("TEST", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
sub, err := js.SubscribeSync("TEST", nats.Durable("cat"))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkSubsPending(t, sub, toSend)
// Do ephemeral too.
esub, err := js.SubscribeSync("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkSubsPending(t, esub, toSend)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "pull", AckPolicy: nats.AckNonePolicy})
require_NoError(t, err)
pullSub, err := js.PullSubscribe("TEST", "pull")
require_NoError(t, err)
// First fetch the messages that are already there.
msgs, err := pullSub.Fetch(toSend, nats.MaxWait(500*time.Millisecond))
require_NoError(t, err)
require_Equal(t, toSend, len(msgs))
// Now prepare a check to see if we get unwated `Consumer Deleted` error on peer remove.
pullResults := make(chan error, 1)
go func() {
_, err := pullSub.Fetch(1, nats.MaxWait(30*time.Second))
// Let's check if we get unwted `Consumer Deleted` error on peer remove.
// Everything else is fine (Leader Changed, Timeout, etc.)
if err != nats.ErrConsumerDeleted {
close(pullResults)
} else {
pullResults <- err
}
}()
ci, err := esub.ConsumerInfo()
if err != nil {
t.Fatalf("Could not fetch consumer info: %v", err)
}
// Capture ephemeral's server and name.
es, en := ci.Cluster.Leader, ci.Name
// Grab stream info.
si, err := js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
peers := []string{si.Cluster.Leader}
for _, p := range si.Cluster.Replicas {
peers = append(peers, p.Name)
}
// Pick a truly random server to remove.
rand.Shuffle(len(peers), func(i, j int) { peers[i], peers[j] = peers[j], peers[i] })
toRemove := peers[0]
// First test bad peer.
req := &JSApiStreamRemovePeerRequest{Peer: "NOT VALID"}
jsreq, err := json.Marshal(req)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Need to call this by hand for now.
resp, err := nc.Request(fmt.Sprintf(JSApiStreamRemovePeerT, "TEST"), jsreq, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var rpResp JSApiStreamRemovePeerResponse
if err := json.Unmarshal(resp.Data, &rpResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if rpResp.Error == nil || !strings.Contains(rpResp.Error.Description, "peer not a member") {
t.Fatalf("Expected error for bad peer, got %+v", rpResp.Error)
}
rpResp.Error = nil
req = &JSApiStreamRemovePeerRequest{Peer: toRemove}
jsreq, err = json.Marshal(req)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
resp, err = nc.Request(fmt.Sprintf(JSApiStreamRemovePeerT, "TEST"), jsreq, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if err := json.Unmarshal(resp.Data, &rpResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if rpResp.Error != nil {
t.Fatalf("Unexpected error: %+v", rpResp.Error)
}
c.waitOnStreamLeader("$G", "TEST")
checkFor(t, 10*time.Second, 100*time.Millisecond, func() error {
si, err := js.StreamInfo("TEST", nats.MaxWait(time.Second))
if err != nil {
return fmt.Errorf("Could not fetch stream info: %v", err)
}
if len(si.Cluster.Replicas) != 2 {
return fmt.Errorf("Expected 2 replicas, got %d", len(si.Cluster.Replicas))
}
for _, peer := range si.Cluster.Replicas {
if !peer.Current {
return fmt.Errorf("Expected replica to be current: %+v", peer)
}
}
if si.Cluster.Leader == toRemove {
return fmt.Errorf("Peer not removed yet: %+v", toRemove)
}
for _, p := range si.Cluster.Replicas {
if p.Name == toRemove {
return fmt.Errorf("Peer not removed yet: %+v", toRemove)
}
}
return nil
})
c.waitOnConsumerLeader("$G", "TEST", "cat")
c.waitOnConsumerLeader("$G", "TEST", "pull")
// Now check consumer info as well.
checkFor(t, 10*time.Second, 100*time.Millisecond, func() error {
ci, err := js.ConsumerInfo("TEST", "cat", nats.MaxWait(time.Second))
if err != nil {
return fmt.Errorf("Could not fetch consumer info: %v", err)
}
if len(ci.Cluster.Replicas) != 2 {
return fmt.Errorf("Expected 2 replicas, got %d", len(ci.Cluster.Replicas))
}
for _, peer := range ci.Cluster.Replicas {
if !peer.Current {
return fmt.Errorf("Expected replica to be current: %+v", peer)
}
}
if ci.Cluster.Leader == toRemove {
return fmt.Errorf("Peer not removed yet: %+v", toRemove)
}
for _, p := range ci.Cluster.Replicas {
if p.Name == toRemove {
return fmt.Errorf("Peer not removed yet: %+v", toRemove)
}
}
return nil
})
// Check if we got the `Consumer Deleted` error on the pull consumer.
select {
case err := <-pullResults:
if err != nil {
t.Fatalf("Expected timeout error or nil, got %v", err)
}
default:
}
// Now check ephemeral consumer info.
// Make sure we did not stamp same new group into the ephemeral where R=1.
ci, err = esub.ConsumerInfo()
// If the leader was same as what we just removed, this should fail.
if es == toRemove {
if err != nats.ErrConsumerNotFound {
t.Fatalf("Expected a not found error, got %v", err)
}
// Also make sure this was removed all together.
// We may proactively move things in the future.
for cn := range js.ConsumerNames("TEST") {
if cn == en {
t.Fatalf("Expected ephemeral consumer to be deleted since we removed its only peer")
}
}
} else {
if err != nil {
t.Fatalf("Could not fetch consumer info: %v", err)
}
if len(ci.Cluster.Replicas) != 0 {
t.Fatalf("Expected no replicas for ephemeral, got %d", len(ci.Cluster.Replicas))
}
}
// Confirm we can peer-remove by the peer ID instead of the server name too.
rs := c.randomNonStreamLeader(globalAccountName, "TEST")
nodeName := rs.Node()
require_Equal(t, nodeName, getHash(rs.Name()))
req = &JSApiStreamRemovePeerRequest{Peer: nodeName}
jsreq, err = json.Marshal(req)
require_NoError(t, err)
resp, err = nc.Request(fmt.Sprintf(JSApiStreamRemovePeerT, "TEST"), jsreq, time.Second)
require_NoError(t, err)
rpResp = JSApiStreamRemovePeerResponse{}
require_NoError(t, json.Unmarshal(resp.Data, &rpResp))
require_True(t, rpResp.Success)
}
func TestJetStreamClusterStreamLeaderStepDown(t *testing.T) {
c := createJetStreamClusterExplicit(t, "RNS", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 3})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Send in 10 messages.
msg, toSend := []byte("Hello JS Clustering"), 10
for i := 0; i < toSend; i++ {
if _, err = js.Publish("TEST", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
sub, err := js.SubscribeSync("TEST", nats.Durable("cat"))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer sub.Unsubscribe()
oldLeader := c.streamLeader("$G", "TEST").Name()
// Need to call this by hand for now.
resp, err := nc.Request(fmt.Sprintf(JSApiStreamLeaderStepDownT, "TEST"), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var sdResp JSApiStreamLeaderStepDownResponse
if err := json.Unmarshal(resp.Data, &sdResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if sdResp.Error != nil {
t.Fatalf("Unexpected error: %+v", sdResp.Error)
}
// Grab shorter timeout jetstream context.
js, err = nc.JetStream(nats.MaxWait(250 * time.Millisecond))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkFor(t, 2*time.Second, 50*time.Millisecond, func() error {
si, err := js.StreamInfo("TEST")
if err != nil {
return fmt.Errorf("Could not fetch stream info: %v", err)
}
if si.Cluster.Leader == oldLeader {
return fmt.Errorf("Still have old leader")
}
if len(si.Cluster.Replicas) != 2 {
return fmt.Errorf("Expected 2 replicas, got %d", len(si.Cluster.Replicas))
}
for _, peer := range si.Cluster.Replicas {
if !peer.Current {
return fmt.Errorf("Expected replica to be current: %+v", peer)
}
}
return nil
})
// Now do consumer.
oldLeader = c.consumerLeader("$G", "TEST", "cat").Name()
// Need to call this by hand for now.
resp, err = nc.Request(fmt.Sprintf(JSApiConsumerLeaderStepDownT, "TEST", "cat"), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var cdResp JSApiConsumerLeaderStepDownResponse
if err := json.Unmarshal(resp.Data, &cdResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if cdResp.Error != nil {
t.Fatalf("Unexpected error: %+v", cdResp.Error)
}
checkFor(t, 2*time.Second, 50*time.Millisecond, func() error {
ci, err := js.ConsumerInfo("TEST", "cat")
if err != nil {
return fmt.Errorf("Could not fetch consumer info: %v", err)
}
if ci.Cluster.Leader == oldLeader {
return fmt.Errorf("Still have old leader")
}
if len(ci.Cluster.Replicas) != 2 {
return fmt.Errorf("Expected 2 replicas, got %d", len(ci.Cluster.Replicas))
}
for _, peer := range ci.Cluster.Replicas {
if !peer.Current {
return fmt.Errorf("Expected replica to be current: %+v", peer)
}
}
return nil
})
}
func TestJetStreamClusterRemoveServer(t *testing.T) {
skip(t)
c := createJetStreamClusterExplicit(t, "RNS", 5)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 3})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Send in 10 messages.
msg, toSend := []byte("Hello JS Clustering"), 10
for i := 0; i < toSend; i++ {
if _, err = js.Publish("TEST", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
sub, err := js.SubscribeSync("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkSubsPending(t, sub, toSend)
ci, err := sub.ConsumerInfo()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
cname := ci.Name
sl := c.streamLeader("$G", "TEST")
c.removeJetStream(sl)
c.waitOnLeader()
c.waitOnStreamLeader("$G", "TEST")
// Faster timeout since we loop below checking for condition.
js, err = nc.JetStream(nats.MaxWait(250 * time.Millisecond))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Check the stream info is eventually correct.
checkFor(t, 20*time.Second, 100*time.Millisecond, func() error {
si, err := js.StreamInfo("TEST")
if err != nil {
return fmt.Errorf("Could not fetch stream info: %v", err)
}
if len(si.Cluster.Replicas) != 2 {
return fmt.Errorf("Expected 2 replicas, got %d", len(si.Cluster.Replicas))
}
for _, peer := range si.Cluster.Replicas {
if !peer.Current {
return fmt.Errorf("Expected replica to be current: %+v", peer)
}
}
return nil
})
// Now do consumer.
c.waitOnConsumerLeader("$G", "TEST", cname)
checkFor(t, 20*time.Second, 50*time.Millisecond, func() error {
ci, err := js.ConsumerInfo("TEST", cname)
if err != nil {
return fmt.Errorf("Could not fetch consumer info: %v", err)
}
if len(ci.Cluster.Replicas) != 2 {
return fmt.Errorf("Expected 2 replicas, got %d", len(ci.Cluster.Replicas))
}
for _, peer := range ci.Cluster.Replicas {
if !peer.Current {
return fmt.Errorf("Expected replica to be current: %+v", peer)
}
}
return nil
})
}
func TestJetStreamClusterPurgeReplayAfterRestart(t *testing.T) {
c := createJetStreamClusterExplicit(t, "P3F", 3)
defer c.shutdown()
// Client based API
s := c.randomNonLeader()
nc, js := jsClientConnect(t, s)
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 3}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sendBatch := func(n int) {
t.Helper()
// Send a batch to a given subject.
for i := 0; i < n; i++ {
if _, err := js.Publish("TEST", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
}
sendBatch(10)
if err := js.PurgeStream("TEST"); err != nil {
t.Fatalf("Unexpected purge error: %v", err)
}
sendBatch(10)
c.stopAll()
c.restartAll()
c.waitOnStreamLeader("$G", "TEST")
s = c.randomServer()
nc, js = jsClientConnect(t, s)
defer nc.Close()
si, err := js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs != 10 {
t.Fatalf("Expected 10 msgs after restart, got %d", si.State.Msgs)
}
}
func TestJetStreamClusterStreamGetMsg(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3F", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST"}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.Publish("TEST", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
mreq := &JSApiMsgGetRequest{Seq: 1}
req, err := json.Marshal(mreq)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
rmsg, err := nc.Request(fmt.Sprintf(JSApiMsgGetT, "TEST"), req, time.Second)
if err != nil {
t.Fatalf("Could not retrieve stream message: %v", err)
}
if err != nil {
t.Fatalf("Could not retrieve stream message: %v", err)
}
var resp JSApiMsgGetResponse
err = json.Unmarshal(rmsg.Data, &resp)
if err != nil {
t.Fatalf("Could not parse stream message: %v", err)
}
if resp.Message == nil || resp.Error != nil {
t.Fatalf("Did not receive correct response: %+v", resp.Error)
}
}
func TestJetStreamClusterStreamDirectGetMsg(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3F", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
// Do by hand for now.
cfg := &StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: MemoryStorage,
Replicas: 3,
MaxMsgsPer: 1,
AllowDirect: true,
}
addStream(t, nc, cfg)
sendStreamMsg(t, nc, "foo", "bar")
getSubj := fmt.Sprintf(JSDirectMsgGetT, "TEST")
getMsg := func(req *JSApiMsgGetRequest) *nats.Msg {
var b []byte
var err error
if req != nil {
b, err = json.Marshal(req)
require_NoError(t, err)
}
m, err := nc.Request(getSubj, b, time.Second)
require_NoError(t, err)
return m
}
m := getMsg(&JSApiMsgGetRequest{LastFor: "foo"})
require_True(t, string(m.Data) == "bar")
require_True(t, m.Header.Get(JSStream) == "TEST")
require_True(t, m.Header.Get(JSSequence) == "1")
require_True(t, m.Header.Get(JSSubject) == "foo")
require_True(t, m.Subject != "foo")
require_True(t, m.Header.Get(JSTimeStamp) != _EMPTY_)
}
func TestJetStreamClusterStreamPerf(t *testing.T) {
// Comment out to run, holding place for now.
skip(t)
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
numConnections := 4
var conns []nats.JetStream
for i := 0; i < numConnections; i++ {
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
conns = append(conns, js)
}
toSend := 100000
numProducers := 8
payload := []byte("Hello JSC")
startCh := make(chan bool)
var wg sync.WaitGroup
for n := 0; n < numProducers; n++ {
wg.Add(1)
go func() {
defer wg.Done()
js := conns[rand.Intn(numConnections)]
<-startCh
for i := 0; i < int(toSend)/numProducers; i++ {
if _, err = js.Publish("foo", payload); err != nil {
t.Errorf("Unexpected publish error: %v", err)
}
}
}()
}
// Wait for Go routines.
time.Sleep(250 * time.Millisecond)
start := time.Now()
close(startCh)
wg.Wait()
tt := time.Since(start)
fmt.Printf("Took %v to send %d msgs with %d producers and R=3!\n", tt, toSend, numProducers)
fmt.Printf("%.0f msgs/sec\n\n", float64(toSend)/tt.Seconds())
}
func TestJetStreamClusterConsumerPerf(t *testing.T) {
// Comment out to run, holding place for now.
skip(t)
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 3})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
toSend := 500000
msg := make([]byte, 64)
crand.Read(msg)
for i := 0; i < toSend; i++ {
nc.Publish("TEST", msg)
}
nc.Flush()
checkFor(t, 10*time.Second, 250*time.Millisecond, func() error {
si, err := js.StreamInfo("TEST")
if err != nil {
return fmt.Errorf("Unexpected error: %v", err)
}
if si.State.Msgs != uint64(toSend) {
return fmt.Errorf("Expected to have %d messages, got %d", toSend, si.State.Msgs)
}
return nil
})
received := int32(0)
deliverTo := "r"
done := make(chan bool)
total := int32(toSend)
var start time.Time
nc.Subscribe(deliverTo, func(m *nats.Msg) {
if r := atomic.AddInt32(&received, 1); r >= total {
done <- true
} else if r == 1 {
start = time.Now()
}
})
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{DeliverSubject: deliverTo, Durable: "gf"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
select {
case <-done:
case <-time.After(10 * time.Second):
t.Fatalf("Timed out?")
}
tt := time.Since(start)
fmt.Printf("Took %v to receive %d msgs\n", tt, toSend)
fmt.Printf("%.0f msgs/sec\n\n", float64(toSend)/tt.Seconds())
}
// This test creates a queue consumer for the delivery subject,
// and make sure it connects to the server that is not the leader
// of the stream. A bug was not stripping the $JS.ACK reply subject
// correctly, which means that ack sent on the reply subject was
// dropped by the route.
func TestJetStreamClusterQueueSubConsumer(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R2S", 2)
defer c.shutdown()
// Client based API
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>"},
Replicas: 1,
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
inbox := nats.NewInbox()
obsReq := CreateConsumerRequest{
Stream: "TEST",
Config: ConsumerConfig{
Durable: "ivan",
DeliverSubject: inbox,
DeliverGroup: "queue",
AckPolicy: AckExplicit,
AckWait: 100 * time.Millisecond,
},
}
req, err := json.Marshal(obsReq)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
resp, err := nc.Request(fmt.Sprintf(JSApiDurableCreateT, "TEST", "ivan"), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var ccResp JSApiConsumerCreateResponse
if err = json.Unmarshal(resp.Data, &ccResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if ccResp.Error != nil {
t.Fatalf("Unexpected error, got %+v", ccResp.Error)
}
ci, err := js.ConsumerInfo("TEST", "ivan")
if err != nil {
t.Fatalf("Error getting consumer info: %v", err)
}
// Now create a client that does NOT connect to the stream leader.
// Start with url from first server in the cluster.
u := c.servers[0].ClientURL()
// If leader is "S-1", then use S-2 to connect to, which is at servers[1].
if ci.Cluster.Leader == "S-1" {
u = c.servers[1].ClientURL()
}
qsubnc, err := nats.Connect(u)
if err != nil {
t.Fatalf("Error connecting: %v", err)
}
defer qsubnc.Close()
ch := make(chan struct{}, 2)
if _, err := qsubnc.QueueSubscribe(inbox, "queue", func(m *nats.Msg) {
m.Respond(nil)
ch <- struct{}{}
}); err != nil {
t.Fatalf("Error creating sub: %v", err)
}
// Use the other connection to publish a message
if _, err := js.Publish("foo.bar", []byte("hello")); err != nil {
t.Fatalf("Error on publish: %v", err)
}
// Wait that we receive the message first.
select {
case <-ch:
case <-time.After(time.Second):
t.Fatal("Did not receive message")
}
// Message should be ack'ed and not redelivered.
select {
case <-ch:
t.Fatal("Message redelivered!!!")
case <-time.After(250 * time.Millisecond):
// OK
}
}
func TestJetStreamClusterLeaderStepdown(t *testing.T) {
c := createJetStreamClusterExplicit(t, "JSC", 3)
defer c.shutdown()
c.waitOnLeader()
cl := c.leader()
// Now ask the system account to have the leader stepdown.
s := c.randomNonLeader()
nc, err := nats.Connect(s.ClientURL(), nats.UserInfo("admin", "s3cr3t!"))
if err != nil {
t.Fatalf("Failed to create system client: %v", err)
}
defer nc.Close()
resp, err := nc.Request(JSApiLeaderStepDown, nil, time.Second)
if err != nil {
t.Fatalf("Error on stepdown request: %v", err)
}
var sdr JSApiLeaderStepDownResponse
if err := json.Unmarshal(resp.Data, &sdr); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if sdr.Error != nil || !sdr.Success {
t.Fatalf("Unexpected error for leader stepdown: %+v", sdr.Error)
}
c.waitOnLeader()
if cl == c.leader() {
t.Fatalf("Expected a new metaleader, got same")
}
}
func TestJetStreamClusterSourcesFilteringAndUpdating(t *testing.T) {
owt := srcConsumerWaitTime
srcConsumerWaitTime = 2 * time.Second
defer func() { srcConsumerWaitTime = owt }()
c := createJetStreamClusterExplicit(t, "MSR", 5)
defer c.shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
sendBatch := func(subject string, n int) {
t.Helper()
// Send a batch to a given subject.
for i := 0; i < n; i++ {
if _, err := js.Publish(subject, []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
}
checkSync := func(msgsTest, msgsM uint64) {
t.Helper()
checkFor(t, 30*time.Second, time.Second, func() error {
if tsi, err := js.StreamInfo("TEST"); err != nil {
return err
} else if msi, err := js.StreamInfo("M"); err != nil {
return err
} else if tsi.State.Msgs != msgsTest {
return fmt.Errorf("received %d msgs from TEST, expected %d", tsi.State.Msgs, msgsTest)
} else if msi.State.Msgs != msgsM {
return fmt.Errorf("received %d msgs from M, expected %d", msi.State.Msgs, msgsM)
}
return nil
})
}
// Origin
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 2,
})
require_NoError(t, err)
defer js.DeleteStream("TEST")
// Create M stream with a single source on "foo"
_, err = js.AddStream(&nats.StreamConfig{
Name: "M",
Sources: []*nats.StreamSource{{Name: "TEST", FilterSubject: "foo"}},
Replicas: 2,
})
require_NoError(t, err)
defer js.DeleteStream("M")
// check a message on "bar" doesn't get sourced
sendBatch("bar", 100)
checkSync(100, 0)
// check a message on "foo" does get sourced
sendBatch("foo", 100)
checkSync(200, 100)
// change remove the source on "foo" and add a new source on "bar"
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "M",
Sources: []*nats.StreamSource{{Name: "TEST", FilterSubject: "bar"}},
Replicas: 2,
})
require_NoError(t, err)
// As it is a new source (never been sourced before) it starts sourcing at the start of TEST
// and therefore sources the message on "bar" that is in TEST
checkSync(200, 200)
// new messages on "foo" are being filtered as it's not being currently sourced
sendBatch("foo", 100)
checkSync(300, 200)
// new messages on "bar" are being sourced
sendBatch("bar", 100)
checkSync(400, 300)
// re-add the source for "foo" keep the source on "bar"
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "M",
Sources: []*nats.StreamSource{{Name: "TEST", FilterSubject: "bar"}, {Name: "TEST", FilterSubject: "foo"}},
Replicas: 2,
})
require_NoError(t, err)
// check the 'backfill' of messages on "foo" that were published while the source was inactive
checkSync(400, 400)
// causes startingSequenceForSources() to be called
nc.Close()
c.stopAll()
c.restartAll()
c.waitOnStreamLeader("$G", "TEST")
c.waitOnStreamLeader("$G", "M")
nc, js = jsClientConnect(t, c.randomServer())
defer nc.Close()
// check that it restarted the sources' consumers at the right place
checkSync(400, 400)
// check both sources are still active
sendBatch("bar", 100)
checkSync(500, 500)
sendBatch("foo", 100)
checkSync(600, 600)
// Check that purging the stream and does not cause the re-sourcing of the messages
js.PurgeStream("M")
checkSync(600, 0)
// Even after a leader change or restart
nc.Close()
c.stopAll()
c.restartAll()
c.waitOnStreamLeader("$G", "TEST")
c.waitOnStreamLeader("$G", "M")
// Allow direct sync consumers to connect.
// This test could pass if we do not hook up and try to deliver the messages when we should not.
time.Sleep(1500 * time.Millisecond)
nc, js = jsClientConnect(t, c.randomServer())
defer nc.Close()
checkSync(600, 0)
}
func TestJetStreamClusterSourcesUpdateOriginError(t *testing.T) {
c := createJetStreamClusterExplicit(t, "MSR", 5)
defer c.shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
sendBatch := func(subject string, n int) {
t.Helper()
// Send a batch to a given subject.
for i := 0; i < n; i++ {
if _, err := js.Publish(subject, []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
}
checkSync := func(msgsTest, msgsM uint64) {
t.Helper()
checkFor(t, 10*time.Second, 500*time.Millisecond, func() error {
if tsi, err := js.StreamInfo("TEST"); err != nil {
return err
} else if msi, err := js.StreamInfo("M"); err != nil {
return err
} else if tsi.State.Msgs != msgsTest {
return fmt.Errorf("received %d msgs from TEST, expected %d", tsi.State.Msgs, msgsTest)
} else if msi.State.Msgs != msgsM {
return fmt.Errorf("received %d msgs from M, expected %d", msi.State.Msgs, msgsM)
}
return nil
})
}
// Origin
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 2,
})
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{
Name: "M",
Sources: []*nats.StreamSource{{Name: "TEST", FilterSubject: "foo"}},
Replicas: 2,
})
require_NoError(t, err)
// Send 100 msgs.
sendBatch("foo", 100)
checkSync(100, 100)
// update makes source invalid
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"bar"},
Replicas: 2,
})
require_NoError(t, err)
// TODO check for downstream error propagation
_, err = js.Publish("foo", nil)
require_Error(t, err)
sendBatch("bar", 100)
// The source stream remains at 100 msgs as it still uses foo as it's filter
checkSync(200, 100)
nc.Close()
c.stopAll()
c.restartAll()
c.waitOnStreamLeader("$G", "TEST")
c.waitOnStreamLeader("$G", "M")
nc, js = jsClientConnect(t, c.randomServer())
defer nc.Close()
checkSync(200, 100)
_, err = js.Publish("foo", nil)
require_Error(t, err)
require_Equal(t, err.Error(), "nats: no response from stream")
sendBatch("bar", 100)
// The source stream remains at 100 msgs as it still uses foo as it's filter
checkSync(300, 100)
}
func TestJetStreamClusterMirrorAndSourcesClusterRestart(t *testing.T) {
owt := srcConsumerWaitTime
srcConsumerWaitTime = 2 * time.Second
defer func() { srcConsumerWaitTime = owt }()
test := func(t *testing.T, mirror bool, filter bool) {
c := createJetStreamClusterExplicit(t, "MSR", 5)
defer c.shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
// Origin
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar", "baz.*"},
Replicas: 2,
})
require_NoError(t, err)
filterSubj := _EMPTY_
if filter {
filterSubj = "foo"
}
// Create Mirror/Source now.
if mirror {
_, err = js.AddStream(&nats.StreamConfig{
Name: "M",
Mirror: &nats.StreamSource{Name: "TEST", FilterSubject: filterSubj},
Replicas: 2,
})
} else {
_, err = js.AddStream(&nats.StreamConfig{
Name: "M",
Sources: []*nats.StreamSource{{Name: "TEST", FilterSubject: filterSubj}},
Replicas: 2,
})
}
require_NoError(t, err)
expectedMsgCount := uint64(0)
sendBatch := func(subject string, n int) {
t.Helper()
if subject == "foo" || !filter {
expectedMsgCount += uint64(n)
}
// Send a batch to a given subject.
for i := 0; i < n; i++ {
if _, err := js.Publish(subject, []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
}
checkSync := func(msgsTest, msgsM uint64) {
t.Helper()
checkFor(t, 40*time.Second, time.Second, func() error {
if tsi, err := js.StreamInfo("TEST"); err != nil {
return err
} else if msi, err := js.StreamInfo("M"); err != nil {
return err
} else if tsi.State.Msgs != msgsTest {
return fmt.Errorf("received %d msgs from TEST, expected %d", tsi.State.Msgs, msgsTest)
} else if msi.State.Msgs != msgsM {
return fmt.Errorf("received %d msgs from M, expected %d", msi.State.Msgs, msgsM)
}
return nil
})
}
sendBatch("foo", 100)
checkSync(100, expectedMsgCount)
sendBatch("bar", 100)
checkSync(200, expectedMsgCount)
nc.Close()
c.stopAll()
c.restartAll()
c.waitOnStreamLeader("$G", "TEST")
c.waitOnStreamLeader("$G", "M")
nc, js = jsClientConnect(t, c.randomServer())
defer nc.Close()
checkSync(200, expectedMsgCount)
sendBatch("foo", 100)
checkSync(300, expectedMsgCount)
sendBatch("bar", 100)
checkSync(400, expectedMsgCount)
}
t.Run("mirror-filter", func(t *testing.T) {
test(t, true, true)
})
t.Run("mirror-nofilter", func(t *testing.T) {
test(t, true, false)
})
t.Run("source-filter", func(t *testing.T) {
test(t, false, true)
})
t.Run("source-nofilter", func(t *testing.T) {
test(t, false, false)
})
}
func TestJetStreamClusterMirrorAndSourcesFilteredConsumers(t *testing.T) {
c := createJetStreamClusterWithTemplate(t, jsClusterMirrorSourceImportsTempl, "MS5", 5)
defer c.shutdown()
// Client for API requests.
s := c.randomServer()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Origin
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar", "baz.*"},
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Create Mirror now.
_, err = js.AddStream(&nats.StreamConfig{
Name: "M",
Mirror: &nats.StreamSource{Name: "TEST"},
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
dsubj := nats.NewInbox()
nc.SubscribeSync(dsubj)
nc.Flush()
createConsumer := func(sn, fs string) {
t.Helper()
_, err = js.AddConsumer(sn, &nats.ConsumerConfig{DeliverSubject: dsubj, FilterSubject: fs})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
expectFail := func(sn, fs string) {
t.Helper()
_, err = js.AddConsumer(sn, &nats.ConsumerConfig{DeliverSubject: dsubj, FilterSubject: fs})
if err == nil {
t.Fatalf("Expected error but got none")
}
}
createConsumer("M", "foo")
createConsumer("M", "bar")
createConsumer("M", "baz.foo")
expectFail("M", ".")
expectFail("M", ">.foo")
// Make sure wider scoped subjects work as well.
createConsumer("M", "*")
createConsumer("M", ">")
// Now do some sources.
if _, err := js.AddStream(&nats.StreamConfig{Name: "O1", Subjects: []string{"foo.*"}}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, err := js.AddStream(&nats.StreamConfig{Name: "O2", Subjects: []string{"bar.*"}}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Create downstream now.
_, err = js.AddStream(&nats.StreamConfig{
Name: "S",
Sources: []*nats.StreamSource{{Name: "O1"}, {Name: "O2"}},
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
createConsumer("S", "foo.1")
createConsumer("S", "bar.1")
// Now cross account stuff.
nc2, js2 := jsClientConnect(t, s, nats.UserInfo("rip", "pass"))
defer nc2.Close()
if _, err := js2.AddStream(&nats.StreamConfig{Name: "ORIGIN", Subjects: []string{"foo.*"}}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
cfg := StreamConfig{
Name: "SCA",
Storage: FileStorage,
Sources: []*StreamSource{{
Name: "ORIGIN",
External: &ExternalStream{
ApiPrefix: "RI.JS.API",
DeliverPrefix: "RI.DELIVER.SYNC.SOURCES",
},
}},
}
req, err := json.Marshal(cfg)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
resp, err := nc.Request(fmt.Sprintf(JSApiStreamCreateT, cfg.Name), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var scResp JSApiStreamCreateResponse
if err := json.Unmarshal(resp.Data, &scResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if scResp.StreamInfo == nil || scResp.Error != nil {
t.Fatalf("Did not receive correct response: %+v", scResp.Error)
}
// Externals skip the checks for now.
createConsumer("SCA", "foo.1")
createConsumer("SCA", "bar.1")
createConsumer("SCA", "baz")
}
func TestJetStreamClusterCrossAccountMirrorsAndSources(t *testing.T) {
c := createJetStreamClusterWithTemplate(t, jsClusterMirrorSourceImportsTempl, "C1", 3)
defer c.shutdown()
// Create source stream under RI account.
s := c.randomServer()
nc, js := jsClientConnect(t, s, nats.UserInfo("rip", "pass"))
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 2}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// use large number to tease out FC issues
toSend := 3000
for i := 0; i < toSend; i++ {
if _, err := js.Publish("TEST", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
nc2, js2 := jsClientConnect(t, s)
defer nc2.Close()
// Have to do this direct until we get Go client support.
// Need to match jsClusterMirrorSourceImportsTempl imports.
_, err := js2.AddStream(&nats.StreamConfig{
Name: "MY_MIRROR_TEST",
Mirror: &nats.StreamSource{
Name: "TEST",
External: &nats.ExternalStream{
APIPrefix: "RI.JS.API",
DeliverPrefix: "RI.DELIVER.SYNC.MIRRORS",
},
},
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkFor(t, 20*time.Second, 500*time.Millisecond, func() error {
si, err := js2.StreamInfo("MY_MIRROR_TEST")
if err != nil {
t.Fatalf("Could not retrieve stream info: %s", err)
}
if si.State.Msgs != uint64(toSend) {
return fmt.Errorf("Expected %d msgs, got state: %+v", toSend, si.State)
}
return nil
})
// Now do sources as well.
_, err = js2.AddStream(&nats.StreamConfig{
Name: "MY_SOURCE_TEST",
Sources: []*nats.StreamSource{
{
Name: "TEST",
External: &nats.ExternalStream{
APIPrefix: "RI.JS.API",
DeliverPrefix: "RI.DELIVER.SYNC.SOURCES",
},
},
},
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkFor(t, 20*time.Second, 100*time.Millisecond, func() error {
si, err := js2.StreamInfo("MY_SOURCE_TEST")
if err != nil {
t.Fatalf("Could not retrieve stream info")
}
if si.State.Msgs != uint64(toSend) {
return fmt.Errorf("Expected %d msgs, got state: %+v", toSend, si.State)
}
return nil
})
}
func TestJetStreamClusterFailMirrorsAndSources(t *testing.T) {
c := createJetStreamClusterWithTemplate(t, jsClusterMirrorSourceImportsTempl, "C1", 3)
defer c.shutdown()
// Create source stream under RI account.
s := c.randomServer()
nc, js := jsClientConnect(t, s, nats.UserInfo("rip", "pass"))
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 2, Subjects: []string{"test.>"}}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
nc2, _ := jsClientConnect(t, s, nats.UserInfo("rip", "pass"))
defer nc2.Close()
testPrefix := func(testName string, id ErrorIdentifier, cfg StreamConfig) {
t.Run(testName, func(t *testing.T) {
req, err := json.Marshal(cfg)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
resp, err := nc2.Request(fmt.Sprintf(JSApiStreamCreateT, cfg.Name), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var scResp JSApiStreamCreateResponse
if err := json.Unmarshal(resp.Data, &scResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if scResp.Error == nil {
t.Fatalf("Did expect an error but got none")
} else if !IsNatsErr(scResp.Error, id) {
t.Fatalf("Expected different error: %s", scResp.Error.Description)
}
})
}
testPrefix("mirror-bad-apiprefix", JSStreamExternalApiOverlapErrF, StreamConfig{
Name: "MY_MIRROR_TEST",
Storage: FileStorage,
Mirror: &StreamSource{
Name: "TEST",
External: &ExternalStream{
ApiPrefix: "$JS.API",
DeliverPrefix: "here",
},
},
})
testPrefix("source-bad-apiprefix", JSStreamExternalApiOverlapErrF, StreamConfig{
Name: "MY_SOURCE_TEST",
Storage: FileStorage,
Sources: []*StreamSource{{
Name: "TEST",
External: &ExternalStream{
ApiPrefix: "$JS.API",
DeliverPrefix: "here",
},
},
},
})
}
func TestJetStreamClusterConsumerDeliveredSyncReporting(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Retention: nats.WorkQueuePolicy,
Subjects: []string{"foo.*"},
Replicas: 3,
})
require_NoError(t, err)
sub, err := js.PullSubscribe("foo.bar", "mc")
require_NoError(t, err)
// Make us match first, but not next 10.
_, err = js.Publish("foo.bar", nil)
require_NoError(t, err)
for i := 0; i < 10; i++ {
_, err = js.Publish("foo.baz", nil)
require_NoError(t, err)
}
opts := &JSzOptions{Accounts: true, Streams: true, Consumer: true}
for _, s := range c.servers {
jsz, err := s.Jsz(opts)
require_NoError(t, err)
ci := jsz.AccountDetails[0].Streams[0].Consumer[0]
require_Equal(t, ci.Delivered.Consumer, 0)
require_Equal(t, ci.Delivered.Stream, 0)
}
msgs, err := sub.Fetch(1)
require_NoError(t, err)
require_Equal(t, len(msgs), 1)
meta, err := msgs[0].Metadata()
require_NoError(t, err)
require_Equal(t, meta.Sequence.Consumer, 1)
require_Equal(t, meta.Sequence.Stream, 1)
// Allow some time for the state to propagate.
maxWait := 200 * time.Millisecond
time.Sleep(maxWait)
for _, s := range c.servers {
jsz, err := s.Jsz(opts)
require_NoError(t, err)
ci := jsz.AccountDetails[0].Streams[0].Consumer[0]
require_Equal(t, ci.Delivered.Consumer, 1)
require_Equal(t, ci.Delivered.Stream, 1)
}
// Now we want to make sure that jsz reporting will show the same
// state, including delivered, which will have skipped to the end.
// The skip can happen on several factors, but for here we just send
// another pull request which we will let fail.
_, err = sub.Fetch(1, nats.MaxWait(maxWait))
require_Error(t, err)
for _, s := range c.servers {
jsz, err := s.Jsz(opts)
require_NoError(t, err)
ci := jsz.AccountDetails[0].Streams[0].Consumer[0]
require_Equal(t, ci.Delivered.Consumer, 1)
require_Equal(t, ci.Delivered.Stream, 11)
}
}
// This is to test follower ack fill logic when pending not empty.
// There was a bug that would update p.Sequence in the stores (mem & file)
// that would cause the logic to fail. Redeliveries were required to trigger.
func TestJetStreamClusterConsumerAckSyncReporting(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Retention: nats.WorkQueuePolicy,
Subjects: []string{"foo.*"},
Replicas: 3,
})
require_NoError(t, err)
sub, err := js.PullSubscribe("foo.bar", "mc", nats.AckWait(250*time.Millisecond))
require_NoError(t, err)
for i := 0; i < 10; i++ {
_, err = js.Publish("foo.bar", nil)
require_NoError(t, err)
}
// Wait for all servers to converge on the same state. Stream and consumer leader could be different.
checkFor(t, time.Second, 100*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "TEST")
})
msgs, err := sub.Fetch(10)
require_NoError(t, err)
require_Equal(t, len(msgs), 10)
// Let redeliveries kick in.
time.Sleep(time.Second)
msgs, err = sub.Fetch(10)
require_NoError(t, err)
require_Equal(t, len(msgs), 10)
// Randomize
rand.Shuffle(len(msgs), func(i, j int) { msgs[i], msgs[j] = msgs[j], msgs[i] })
dontAck := uint64(7)
var skipped, last *nats.Msg
for _, m := range msgs {
meta, err := m.Metadata()
require_NoError(t, err)
if meta.Sequence.Stream == dontAck {
skipped = m
continue
}
if meta.Sequence.Stream == 10 {
last = m
continue
}
err = m.AckSync()
require_NoError(t, err)
}
require_NotNil(t, skipped)
require_NotNil(t, last)
checkAckFloor := func(consumer, stream uint64) {
opts := &JSzOptions{Accounts: true, Streams: true, Consumer: true}
checkFor(t, 3*time.Second, 200*time.Millisecond, func() error {
for _, s := range c.servers {
jsz, err := s.Jsz(opts)
if err != nil {
return err
}
ci := jsz.AccountDetails[0].Streams[0].Consumer[0]
if ci.AckFloor.Consumer != consumer {
return fmt.Errorf("AckFloor.Consumer is not %d: %v", consumer, ci.AckFloor.Consumer)
}
if ci.AckFloor.Stream != stream {
return fmt.Errorf("AckFloor.Stream is not %d: %v", stream, ci.AckFloor.Stream)
}
}
return nil
})
}
// Now we want to make sure that jsz reporting will show the same
// state for ack floor.
checkAckFloor(dontAck-1, dontAck-1)
// Now ack the skipped message
err = skipped.AckSync()
require_NoError(t, err)
checkAckFloor(9, 9)
// Now ack the last message
err = last.AckSync()
require_NoError(t, err)
checkAckFloor(20, 10)
}
func TestJetStreamClusterConsumerDeleteInterestPolicyMultipleConsumers(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Retention: nats.InterestPolicy,
Subjects: []string{"foo.*"},
Replicas: 3,
})
require_NoError(t, err)
// Make the first sequence high. We already protect against it but for extra sanity.
err = js.PurgeStream("TEST", &nats.StreamPurgeRequest{Sequence: 100_000_000})
require_NoError(t, err)
// Create 2 consumers.
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "C1",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "C2",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
for i := 0; i < 100; i++ {
js.PublishAsync("foo.bar", []byte("ok"))
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
expectedStreamMsgs := func(msgs uint64) {
t.Helper()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
si, err := js.StreamInfo("TEST")
if err != nil {
return err
}
if si.State.Msgs != msgs {
return fmt.Errorf("require uint64 equal, but got: %d != %d", si.State.Msgs, msgs)
}
return nil
})
}
expectedStreamMsgs(100)
sub, err := js.PullSubscribe("foo.bar", "C1")
require_NoError(t, err)
msgs, err := sub.Fetch(50)
require_NoError(t, err)
require_Equal(t, len(msgs), 50)
for _, m := range msgs {
require_NoError(t, m.AckSync())
}
// Now delete second one and make sure accounting correct.
err = js.DeleteConsumer("TEST", "C2")
require_NoError(t, err)
expectedStreamMsgs(50)
}
func TestJetStreamClusterConsumerAckNoneInterestPolicyShouldNotRetainAfterDelivery(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Retention: nats.InterestPolicy,
Subjects: []string{"foo.*"},
Replicas: 3,
})
require_NoError(t, err)
// Make the first sequence high. We already protect against it but for extra sanity.
err = js.PurgeStream("TEST", &nats.StreamPurgeRequest{Sequence: 100_000_000})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "C1",
AckPolicy: nats.AckNonePolicy,
})
require_NoError(t, err)
for i := 0; i < 100; i++ {
js.PublishAsync("foo.bar", []byte("ok"))
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 100)
sub, err := js.PullSubscribe("foo.bar", "C1")
require_NoError(t, err)
msgs, err := sub.Fetch(100)
require_NoError(t, err)
require_Equal(t, len(msgs), 100)
checkFor(t, time.Second, 100*time.Millisecond, func() error {
si, err = js.StreamInfo("TEST")
if err != nil {
return err
}
if si.State.Msgs != 0 {
return fmt.Errorf("require uint64 equal, but got: %d != 0", si.State.Msgs)
}
return nil
})
}
func TestJetStreamClusterConsumerDeleteAckNoneInterestPolicyWithOthers(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Retention: nats.InterestPolicy,
Subjects: []string{"foo.*"},
Replicas: 3,
})
require_NoError(t, err)
// Make the first sequence high. We already protect against it but for extra sanity.
err = js.PurgeStream("TEST", &nats.StreamPurgeRequest{Sequence: 100_000_000})
require_NoError(t, err)
// Create 2 consumers.
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "C1",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "C2",
AckPolicy: nats.AckNonePolicy,
})
require_NoError(t, err)
for i := 0; i < 100; i++ {
js.PublishAsync("foo.bar", []byte("ok"))
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
expectedStreamMsgs := func(msgs uint64) {
t.Helper()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
si, err := js.StreamInfo("TEST")
if err != nil {
return err
}
if si.State.Msgs != msgs {
return fmt.Errorf("require uint64 equal, but got: %d != %d", si.State.Msgs, msgs)
}
return nil
})
}
expectedStreamMsgs(100)
sub, err := js.PullSubscribe("foo.bar", "C1")
require_NoError(t, err)
msgs, err := sub.Fetch(100)
require_NoError(t, err)
require_Equal(t, len(msgs), 100)
for _, m := range msgs {
require_NoError(t, m.AckSync())
}
// AckNone will hold.
expectedStreamMsgs(100)
// Now delete second one and make sure accounting correct.
err = js.DeleteConsumer("TEST", "C2")
require_NoError(t, err)
expectedStreamMsgs(0)
}
// Make sure to not allow non-system accounts to move meta leader.
func TestJetStreamClusterMetaStepdownFromNonSysAccount(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
s := c.randomServer()
// Client based API
nc, _ := jsClientConnect(t, s)
defer nc.Close()
ml := c.leader()
_, err := nc.Request(JSApiLeaderStepDown, nil, time.Second)
require_Error(t, err, nats.ErrTimeout)
// Make sure we did not move.
c.waitOnLeader()
require_Equal(t, ml, c.leader())
// System user can move it.
snc, _ := jsClientConnect(t, c.randomServer(), nats.UserInfo("admin", "s3cr3t!"))
defer snc.Close()
resp, err := snc.Request(JSApiLeaderStepDown, nil, time.Second)
require_NoError(t, err)
var sdr JSApiLeaderStepDownResponse
require_NoError(t, json.Unmarshal(resp.Data, &sdr))
require_True(t, sdr.Success)
require_Equal(t, sdr.Error, nil)
// Make sure we did move this time.
c.waitOnLeader()
require_NotEqual(t, ml, c.leader())
}
func TestJetStreamClusterMaxDeliveriesOnInterestStreams(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Client based API
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
Retention: nats.InterestPolicy,
})
require_NoError(t, err)
sub1, err := js.PullSubscribe("foo.*", "c1", nats.AckWait(10*time.Millisecond), nats.MaxDeliver(1))
require_NoError(t, err)
sub2, err := js.PullSubscribe("foo.*", "c2", nats.AckWait(10*time.Millisecond), nats.MaxDeliver(1))
require_NoError(t, err)
_, err = js.Publish("foo.bar", []byte("HELLO"))
require_NoError(t, err)
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 1)
msgs, err := sub1.Fetch(1)
require_NoError(t, err)
require_Equal(t, len(msgs), 1)
msgs, err = sub2.Fetch(1)
require_NoError(t, err)
require_Equal(t, len(msgs), 1)
// Wait for redelivery to both consumers which will do nothing.
time.Sleep(250 * time.Millisecond)
// Now check that stream and consumer infos are correct.
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
// Messages that are skipped due to max deliveries should NOT remove messages.
require_Equal(t, si.State.Msgs, 1)
require_Equal(t, si.State.Consumers, 2)
for _, cname := range []string{"c1", "c2"} {
ci, err := js.ConsumerInfo("TEST", cname)
require_NoError(t, err)
require_Equal(t, ci.Delivered.Consumer, 1)
require_Equal(t, ci.Delivered.Stream, 1)
require_Equal(t, ci.AckFloor.Consumer, 0)
require_Equal(t, ci.AckFloor.Stream, 0)
require_Equal(t, ci.NumAckPending, 0)
require_Equal(t, ci.NumRedelivered, 0)
require_Equal(t, ci.NumPending, 0)
}
}
func TestJetStreamClusterMetaRecoveryUpdatesDeletesConsumers(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
js := c.leader().getJetStream()
create := []*Entry{
{EntryNormal, encodeAddStreamAssignment(&streamAssignment{
Config: &StreamConfig{Name: "TEST", Storage: FileStorage},
})},
{EntryNormal, encodeAddConsumerAssignment(&consumerAssignment{
Stream: "TEST",
Config: &ConsumerConfig{Name: "consumer"},
})},
}
delete := []*Entry{
{EntryNormal, encodeDeleteStreamAssignment(&streamAssignment{
Config: &StreamConfig{Name: "TEST", Storage: FileStorage},
})},
}
// Need to be recovering so that we accumulate recoveryUpdates.
js.setMetaRecovering()
ru := &recoveryUpdates{
removeStreams: make(map[string]*streamAssignment),
removeConsumers: make(map[string]map[string]*consumerAssignment),
addStreams: make(map[string]*streamAssignment),
updateStreams: make(map[string]*streamAssignment),
updateConsumers: make(map[string]map[string]*consumerAssignment),
}
// Push recovery entries that create the stream & consumer.
_, _, err := js.applyMetaEntries(create, ru)
require_NoError(t, err)
require_Len(t, len(ru.updateConsumers), 1)
// Now push another recovery entry that deletes the stream. The
// entry that creates the consumer should now be gone.
_, _, err = js.applyMetaEntries(delete, ru)
require_NoError(t, err)
require_Len(t, len(ru.removeStreams), 1)
require_Len(t, len(ru.updateConsumers), 0)
}
func TestJetStreamClusterMetaRecoveryRecreateFileStreamAsMemory(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
js := c.leader().getJetStream()
createFileStream := []*Entry{
{EntryNormal, encodeAddStreamAssignment(&streamAssignment{
Config: &StreamConfig{Name: "TEST", Storage: FileStorage},
})},
}
deleteFileStream := []*Entry{
{EntryNormal, encodeDeleteStreamAssignment(&streamAssignment{
Config: &StreamConfig{Name: "TEST", Storage: FileStorage},
})},
}
createMemoryStream := []*Entry{
{EntryNormal, encodeAddStreamAssignment(&streamAssignment{
Config: &StreamConfig{Name: "TEST", Storage: FileStorage},
})},
}
createConsumer := []*Entry{
{EntryNormal, encodeAddConsumerAssignment(&consumerAssignment{
Stream: "TEST",
Config: &ConsumerConfig{Name: "consumer"},
})},
}
// Need to be recovering so that we accumulate recoveryUpdates.
js.setMetaRecovering()
ru := &recoveryUpdates{
removeStreams: make(map[string]*streamAssignment),
removeConsumers: make(map[string]map[string]*consumerAssignment),
addStreams: make(map[string]*streamAssignment),
updateStreams: make(map[string]*streamAssignment),
updateConsumers: make(map[string]map[string]*consumerAssignment),
}
// We created a file-based stream first, but deleted it shortly after.
_, _, err := js.applyMetaEntries(createFileStream, ru)
require_NoError(t, err)
require_Len(t, len(ru.addStreams), 1)
require_Len(t, len(ru.removeStreams), 0)
// Now push another recovery entry that deletes the stream.
// The file-based stream should not have been created.
_, _, err = js.applyMetaEntries(deleteFileStream, ru)
require_NoError(t, err)
require_Len(t, len(ru.addStreams), 0)
require_Len(t, len(ru.removeStreams), 1)
// Now stage a memory-based stream to be created.
_, _, err = js.applyMetaEntries(createMemoryStream, ru)
require_NoError(t, err)
require_Len(t, len(ru.addStreams), 1)
require_Len(t, len(ru.removeStreams), 1)
require_Len(t, len(ru.updateConsumers), 0)
// Also create a consumer on that memory-based stream.
_, _, err = js.applyMetaEntries(createConsumer, ru)
require_NoError(t, err)
require_Len(t, len(ru.addStreams), 1)
require_Len(t, len(ru.removeStreams), 1)
require_Len(t, len(ru.updateConsumers), 1)
}
func TestJetStreamClusterMetaRecoveryConsumerCreateAndRemove(t *testing.T) {
tests := []struct {
title string
encodeAddConsumerAssignment func(ca *consumerAssignment) []byte
}{
{title: "simple", encodeAddConsumerAssignment: encodeAddConsumerAssignment},
{title: "compressed", encodeAddConsumerAssignment: encodeAddConsumerAssignmentCompressed},
}
for _, test := range tests {
t.Run(test.title, func(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
js := c.leader().getJetStream()
ca := &consumerAssignment{Stream: "TEST", Name: "consumer"}
createConsumer := []*Entry{{EntryNormal, test.encodeAddConsumerAssignment(ca)}}
deleteConsumer := []*Entry{{EntryNormal, encodeDeleteConsumerAssignment(ca)}}
// Need to be recovering so that we accumulate recoveryUpdates.
js.setMetaRecovering()
ru := &recoveryUpdates{
removeStreams: make(map[string]*streamAssignment),
removeConsumers: make(map[string]map[string]*consumerAssignment),
addStreams: make(map[string]*streamAssignment),
updateStreams: make(map[string]*streamAssignment),
updateConsumers: make(map[string]map[string]*consumerAssignment),
}
// Creating the consumer should append to update consumers list.
_, _, err := js.applyMetaEntries(createConsumer, ru)
require_NoError(t, err)
require_Len(t, len(ru.updateConsumers[":TEST"]), 1)
require_Len(t, len(ru.removeConsumers), 0)
// Deleting the consumer should append to remove consumers list and remove from update list.
_, _, err = js.applyMetaEntries(deleteConsumer, ru)
require_NoError(t, err)
require_Len(t, len(ru.removeConsumers[":TEST"]), 1)
require_Len(t, len(ru.updateConsumers[":TEST"]), 0)
// When re-creating the consumer, add to update list.
_, _, err = js.applyMetaEntries(createConsumer, ru)
require_NoError(t, err)
require_Len(t, len(ru.removeConsumers[":TEST"]), 1)
require_Len(t, len(ru.updateConsumers[":TEST"]), 1)
})
}
}
func TestJetStreamClusterMetaRecoveryAddAndUpdateStream(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
js := c.leader().getJetStream()
ci := &ClientInfo{Account: globalAccountName, Cluster: "R3S"}
cfg := &StreamConfig{Name: "TEST", Storage: FileStorage, Replicas: 3}
rg, perr := js.createGroupForStream(ci, cfg)
if perr != nil {
require_NoError(t, perr)
}
sa := &streamAssignment{Client: ci, Config: cfg, Group: rg}
// Need to be recovering so that we accumulate recoveryUpdates.
js.setMetaRecovering()
ru := &recoveryUpdates{
removeStreams: make(map[string]*streamAssignment),
removeConsumers: make(map[string]map[string]*consumerAssignment),
addStreams: make(map[string]*streamAssignment),
updateStreams: make(map[string]*streamAssignment),
updateConsumers: make(map[string]map[string]*consumerAssignment),
}
// We create a new stream, and we'll update it shortly after.
entries := []*Entry{{EntryNormal, encodeAddStreamAssignment(sa)}}
_, _, err := js.applyMetaEntries(entries, ru)
require_NoError(t, err)
require_Len(t, len(ru.addStreams), 1)
require_Len(t, len(ru.updateStreams), 0)
require_Len(t, len(ru.removeStreams), 0)
// Now update the stream. The recovery updates should contain both the add and update.
// If only the update would exist, the stream would not be created below.
sa.Config.Subjects = []string{"foo"}
entries = []*Entry{{EntryNormal, encodeUpdateStreamAssignment(sa)}}
_, _, err = js.applyMetaEntries(entries, ru)
require_NoError(t, err)
require_Len(t, len(ru.addStreams), 1)
require_Len(t, len(ru.updateStreams), 1)
require_Len(t, len(ru.removeStreams), 0)
// Check the stream is properly added.
for _, sa := range ru.addStreams {
js.processStreamAssignment(sa)
}
sa = js.streamAssignment(globalAccountName, "TEST")
require_NotNil(t, sa)
require_Len(t, len(sa.Config.Subjects), 0)
// Check the stream is properly updated.
for _, sa := range ru.updateStreams {
js.processUpdateStreamAssignment(sa)
}
sa = js.streamAssignment(globalAccountName, "TEST")
require_NotNil(t, sa)
require_Len(t, len(sa.Config.Subjects), 1)
}
// https://github.com/nats-io/nats-server/issues/7229
func TestJetStreamClusterProcessClusterUpdateStreamNilStreamAssignment(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}, Replicas: 3})
require_NoError(t, err)
s := c.randomServer()
acc, err := s.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
// Capture osa and build a sa that reflects the post-restart meta-replay
// conditions: a scale-up path where both the old and new raftGroup nodes
// are nil on this server (alreadyRunning=false, needsNode=true).
sjs := s.getJetStream()
sjs.mu.Lock()
realOsa := sjs.streamAssignment(globalAccountName, "TEST")
if realOsa == nil {
sjs.mu.Unlock()
t.Fatal("stream assignment not found")
}
// Intentionally omit Group.node so that in processClusterUpdateStream
// alreadyRunning (osa.Group.node != nil) is false and needsNode
// (sa.Group.node == nil) is true, taking the scale-up branch.
cloneGroup := func() *raftGroup {
return &raftGroup{
Name: realOsa.Group.Name,
Peers: append([]string{}, realOsa.Group.Peers...),
Storage: realOsa.Group.Storage,
}
}
osa := realOsa.clone()
osa.Group = cloneGroup()
sa := osa.clone()
cfg := *realOsa.Config
cfg.MaxMsgs = 1_000
sa.Config = &cfg
sa.Group = cloneGroup()
sjs.mu.Unlock()
// Simulate the state right after recoverStream() before meta catchup:
// no stream assignment, no raft node, no cluster subs registered yet.
mset.mu.Lock()
mset.sa = nil
mset.node = nil
if mset.syncSub != nil {
mset.srv.sysUnsubscribe(mset.syncSub)
mset.syncSub = nil
}
mset.mu.Unlock()
sjs.processClusterUpdateStream(acc, osa, sa)
mset.mu.RLock()
got := mset.sa
mset.mu.RUnlock()
require_NotNil(t, got)
}
// Make sure if we received acks that are out of bounds, meaning past our
// last sequence or before our first that they are ignored and errored if applicable.
func TestJetStreamClusterConsumerAckOutOfBounds(t *testing.T) {
testJetStreamClusterConsumerAckOutOfBounds(t, false)
}
func TestJetStreamClusterConsumerAckV2OutOfBounds(t *testing.T) {
testJetStreamClusterConsumerAckOutOfBounds(t, true)
}
func testJetStreamClusterConsumerAckOutOfBounds(t *testing.T, ackV2 bool) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
for _, s := range c.servers {
opts := *s.getOpts()
opts.FeatureFlags = map[string]bool{FeatureFlagJsAckFormatV2: ackV2}
s.setOpts(&opts)
}
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
Retention: nats.WorkQueuePolicy,
Replicas: 3,
})
require_NoError(t, err)
for i := 0; i < 10; i++ {
_, err = js.Publish("foo.bar", []byte("OK"))
require_NoError(t, err)
}
sub, err := js.PullSubscribe("foo.*", "C")
require_NoError(t, err)
msgs, err := sub.Fetch(1)
require_NoError(t, err)
require_Equal(t, len(msgs), 1)
msgs[0].AckSync()
// Now ack way past the last sequence.
cl := c.consumerLeader(globalAccountName, "TEST", "C")
require_NotNil(t, cl)
mset, err := cl.globalAccount().lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("C")
require_NotNil(t, o)
o.mu.RLock()
ackReply := o.ackReply(10000000000, 0, 1, 0, 0)
o.mu.RUnlock()
if ackV2 {
require_Equal(t, ackReply, "$JS.ACK._.szMpdrwD.TEST.C.1.10000000000.0.0.0")
} else {
require_Equal(t, ackReply, "$JS.ACK.TEST.C.1.10000000000.0.0.0")
}
_, err = nc.Request(ackReply, nil, 250*time.Millisecond)
require_Error(t, err, nats.ErrTimeout)
// Make sure that now changes happened to our state.
ci, err := js.ConsumerInfo("TEST", "C")
require_NoError(t, err)
require_Equal(t, ci.Delivered.Consumer, 1)
require_Equal(t, ci.Delivered.Stream, 1)
require_Equal(t, ci.AckFloor.Consumer, 1)
require_Equal(t, ci.AckFloor.Stream, 1)
s := c.consumerLeader("$G", "TEST", "C")
s.Shutdown()
s.WaitForShutdown()
c.restartServer(s)
c.waitOnConsumerLeader(globalAccountName, "TEST", "C")
// Confirm new leader has same state for delivered and ack floor.
ci, err = js.ConsumerInfo("TEST", "C")
require_NoError(t, err)
require_Equal(t, ci.Delivered.Consumer, 1)
require_Equal(t, ci.Delivered.Stream, 1)
require_Equal(t, ci.AckFloor.Consumer, 1)
require_Equal(t, ci.AckFloor.Stream, 1)
}
func TestJetStreamClusterCatchupLoadNextMsgTooManyDeletes(t *testing.T) {
tests := []struct {
title string
catchupRequest *streamSyncRequest
setup func(js nats.JetStreamContext)
assert func(sub *nats.Subscription)
}{
{
title: "within-delete-gap",
setup: func(js nats.JetStreamContext) {},
},
{
title: "EOF",
setup: func(js nats.JetStreamContext) {
err := js.DeleteMsg("TEST", 100)
require_NoError(t, err)
},
},
}
for _, test := range tests {
t.Run(test.title, func(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 3,
})
require_NoError(t, err)
// Starts and ends with subject "foo", we'll purge so there's a large gap of deletes in the middle.
// This should force runCatchup to use LoadNextMsg instead of LoadMsg.
for i := 0; i < 100; i++ {
subject := "bar"
if i == 0 || i == 99 {
subject = "foo"
}
_, err = js.Publish(subject, nil)
require_NoError(t, err)
}
err = js.PurgeStream("TEST", &nats.StreamPurgeRequest{Subject: "bar"})
require_NoError(t, err)
// Optionally run some extra setup.
test.setup(js)
// Reconnect to stream leader.
l := c.streamLeader(globalAccountName, "TEST")
nc.Close()
nc, _ = jsClientConnect(t, l, nats.UserInfo("admin", "s3cr3t!"))
defer nc.Close()
// Setup wiretap and grab stream.
sendSubject := "test-wiretap"
sub, err := nc.SubscribeSync(sendSubject)
require_NoError(t, err)
err = nc.Flush() // Must flush, otherwise our subscription could be too late.
require_NoError(t, err)
acc, err := l.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
// Run custom catchup request and the test's asserts.
sreq := &streamSyncRequest{Peer: "peer", FirstSeq: 5, LastSeq: 5, DeleteRangesOk: true}
require_True(t, mset.srv.startGoRoutine(func() { mset.runCatchup(sendSubject, sreq) }))
// Our first message should be a skip msg.
msg, err := sub.NextMsg(time.Second)
require_NoError(t, err)
require_Equal(t, entryOp(msg.Data[0]), streamMsgOp)
subj, _, _, _, seq, ts, _, err := decodeStreamMsg(msg.Data[1:])
require_NoError(t, err)
require_Equal(t, seq, 5)
require_Equal(t, subj, _EMPTY_)
require_Equal(t, ts, 0)
// And end with EOF.
msg, err = sub.NextMsg(time.Second)
require_NoError(t, err)
require_Len(t, len(msg.Data), 0)
})
}
}
func TestJetStreamClusterCatchupMustStallWhenBehindOnApplies(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
_, err = js.Publish("foo", nil)
require_NoError(t, err)
// Reconnect to stream leader.
l := c.streamLeader(globalAccountName, "TEST")
nc.Close()
nc, _ = jsClientConnect(t, l, nats.UserInfo("admin", "s3cr3t!"))
defer nc.Close()
// Setup wiretap and grab stream.
sendSubject := "test-wiretap"
sub, err := nc.SubscribeSync(sendSubject)
require_NoError(t, err)
err = nc.Flush() // Must flush, otherwise our subscription could be too late.
require_NoError(t, err)
acc, err := l.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
// We have a message at sequence 1, so expect a successful catchup.
sreq1 := &streamSyncRequest{Peer: "peer", FirstSeq: 1, LastSeq: 1, DeleteRangesOk: true}
require_True(t, mset.srv.startGoRoutine(func() { mset.runCatchup(sendSubject, sreq1) }))
// Expect the message at sequence 1.
msg, err := sub.NextMsg(time.Second)
require_NoError(t, err)
require_Equal(t, entryOp(msg.Data[0]), streamMsgOp)
subj, _, _, _, seq, _, _, err := decodeStreamMsg(msg.Data[1:])
require_NoError(t, err)
require_Equal(t, seq, 1)
require_Equal(t, subj, "foo")
// And end with EOF.
msg, err = sub.NextMsg(time.Second)
require_NoError(t, err)
require_Len(t, len(msg.Data), 0)
// Add one additional entry into the log that's not applied yet.
n := mset.node.(*raft)
n.Lock()
ae := n.buildAppendEntry(nil)
err = n.storeToWAL(ae)
n.Unlock()
index, commit, applied := n.Progress()
require_NoError(t, err)
require_LessThan(t, applied, index)
require_Equal(t, commit, applied)
// We have a message at sequence 1, but we haven't applied as many append entries.
// We can't fulfill the request right now as we don't know yet if
// that message will be deleted as part of upcoming append entries.
sreq2 := &streamSyncRequest{Peer: "peer", FirstSeq: 1, LastSeq: 1, DeleteRangesOk: true, MinApplied: index}
require_True(t, mset.srv.startGoRoutine(func() { mset.runCatchup(sendSubject, sreq2) }))
_, err = sub.NextMsg(time.Second)
require_Error(t, err, nats.ErrTimeout)
// We have a message at sequence 1, but we haven't applied as many append entries.
// Also, we seem to have a log that doesn't contain enough entries, even though we became leader.
// Something has already gone wrong and got the logs to desync.
// Value availability here and just fulfill the request.
sreq3 := &streamSyncRequest{Peer: "peer", FirstSeq: 1, LastSeq: 1, DeleteRangesOk: true, MinApplied: 100}
require_True(t, mset.srv.startGoRoutine(func() { mset.runCatchup(sendSubject, sreq3) }))
// Expect the message at sequence 1.
msg, err = sub.NextMsg(time.Second)
require_NoError(t, err)
require_Equal(t, entryOp(msg.Data[0]), streamMsgOp)
subj, _, _, _, seq, _, _, err = decodeStreamMsg(msg.Data[1:])
require_NoError(t, err)
require_Equal(t, seq, 1)
require_Equal(t, subj, "foo")
// And end with EOF.
msg, err = sub.NextMsg(time.Second)
require_NoError(t, err)
require_Len(t, len(msg.Data), 0)
}
func TestJetStreamClusterConsumerInfoAfterCreate(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nl := c.randomNonLeader()
nc, js := jsClientConnect(t, nl)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
}
si, err := js.AddStream(cfg)
require_NoError(t, err)
// We want to ensure the consumer can be created and be applied.
// On the non-meta-leader server we'll pause applies, so need to make
// sure the consumer is not created on that server.
if si.Cluster.Leader == nl.Name() {
ml := c.leader()
jreq, err := json.Marshal(&JSApiLeaderStepdownRequest{Placement: &Placement{Preferred: ml.Name()}})
require_NoError(t, err)
resp, err := nc.Request(fmt.Sprintf(JSApiStreamLeaderStepDownT, "TEST"), jreq, time.Second)
require_NoError(t, err)
var sdr JSApiLeaderStepDownResponse
require_NoError(t, json.Unmarshal(resp.Data, &sdr))
}
// Wait some time for the leader to settle.
time.Sleep(500 * time.Millisecond)
// Scale down to ensure the consumer gets created on this server.
cfg.Replicas = 1
si, err = js.UpdateStream(cfg)
require_NoError(t, err)
require_NotEqual(t, si.Cluster.Leader, nl.Name())
// We pause applies for the server we're connected to.
// This is fine for the RAFT log and allowing the consumer to be created,
// but we will not be able to apply the consumer assignment for some time.
mjs := nl.getJetStream()
require_NotNil(t, js)
mg := mjs.getMetaGroup()
require_NotNil(t, mg)
err = mg.(*raft).PauseApply()
require_NoError(t, err)
// Add consumer.
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "CONSUMER"})
require_NoError(t, err)
// Consumer info should not fail, this server should not short-circuit because
// it was not able to apply the consumer assignment.
_, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
// Resume applies.
mg.(*raft).ResumeApply()
// Check consumer info still works.
_, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
}
func TestJetStreamClusterStreamUpscalePeersAfterDownscale(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R5S", 5)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
checkPeerSet := func() {
t.Helper()
checkFor(t, 5*time.Second, 500*time.Millisecond, func() error {
for _, s := range c.servers {
acc, err := s.lookupAccount(globalAccountName)
if err != nil {
return err
}
mset, err := acc.lookupStream("TEST")
if err != nil {
return err
}
peers := mset.raftNode().Peers()
if len(peers) != 5 {
return fmt.Errorf("expected 5 peers, got %d", len(peers))
}
}
return nil
})
}
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 5,
})
require_NoError(t, err)
checkPeerSet()
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 5,
})
require_NoError(t, err)
checkPeerSet()
}
func TestJetStreamClusterClearAllPreAcksOnRemoveMsg(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
Retention: nats.WorkQueuePolicy,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "CONSUMER",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
for i := 0; i < 3; i++ {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
// Wait for all servers to converge on the same state.
checkFor(t, 5*time.Second, 500*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "TEST")
})
// Register pre-acks on all servers.
// Normally this can't happen as the stream leader will have the message that's acked available, just for testing.
for _, s := range c.servers {
acc, err := s.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
// Register pre-acks for the 3 messages.
mset.registerPreAckLock(o, 1)
mset.registerPreAckLock(o, 2)
mset.registerPreAckLock(o, 3)
}
// Check there's an expected amount of pre-acks, and there are no pre-acks for the given sequence.
checkPreAcks := func(seq uint64, expected int) {
t.Helper()
checkFor(t, 5*time.Second, time.Second, func() error {
for _, s := range c.servers {
acc, err := s.lookupAccount(globalAccountName)
if err != nil {
return err
}
mset, err := acc.lookupStream("TEST")
if err != nil {
return err
}
mset.mu.RLock()
numPreAcks := len(mset.preAcks)
numSeqPreAcks := len(mset.preAcks[seq])
mset.mu.RUnlock()
if numPreAcks != expected {
return fmt.Errorf("expected %d pre-acks, got %d", expected, numPreAcks)
}
if seq > 0 && numSeqPreAcks != 0 {
return fmt.Errorf("expected 0 pre-acks for seq %d, got %d", seq, numSeqPreAcks)
}
}
return nil
})
}
// Check all pre-acks were registered.
checkPreAcks(0, 3)
// Deleting the message should clear the pre-ack.
err = js.DeleteMsg("TEST", 1)
require_NoError(t, err)
checkPreAcks(1, 2)
// Erasing the message should clear the pre-ack.
err = js.SecureDeleteMsg("TEST", 2)
require_NoError(t, err)
checkPreAcks(2, 1)
// Purging should clear all pre-acks below the purged floor.
err = js.PurgeStream("TEST", &nats.StreamPurgeRequest{Sequence: 4})
require_NoError(t, err)
checkPreAcks(3, 0)
}
func TestJetStreamClusterStreamHealthCheckMustNotRecreate(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
waitForStreamAssignments := func() {
t.Helper()
checkFor(t, 5*time.Second, time.Second, func() error {
for _, s := range c.servers {
js := s.getJetStream()
js.mu.RLock()
sa := js.streamAssignment(globalAccountName, "TEST")
js.mu.RUnlock()
if sa == nil {
return fmt.Errorf("stream assignment not found on %s", s.Name())
}
}
return nil
})
}
waitForNoStreamAssignments := func() {
t.Helper()
checkFor(t, 5*time.Second, time.Second, func() error {
for _, s := range c.servers {
js := s.getJetStream()
js.mu.RLock()
sa := js.streamAssignment(globalAccountName, "TEST")
js.mu.RUnlock()
if sa != nil {
return fmt.Errorf("stream assignment still available on %s", s.Name())
}
}
return nil
})
}
getStreamAssignment := func(rs *Server) (*jetStream, *Account, *streamAssignment, *stream) {
acc, err := rs.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NotNil(t, err)
sjs := rs.getJetStream()
sjs.mu.RLock()
defer sjs.mu.RUnlock()
sas := sjs.cluster.streams[globalAccountName]
require_True(t, sas != nil)
sa := sas["TEST"]
require_True(t, sa != nil)
sa.Created = time.Time{}
return sjs, acc, sa, mset
}
checkNodeIsClosed := func(sa *streamAssignment) {
t.Helper()
require_True(t, sa.Group != nil)
rg := sa.Group
require_True(t, rg.node != nil)
n := rg.node
require_Equal(t, n.State(), Closed)
}
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
waitForStreamAssignments()
// We manually stop the RAFT node and ensure it doesn't get restarted.
rs := c.randomNonStreamLeader(globalAccountName, "TEST")
sjs, acc, sa, mset := getStreamAssignment(rs)
require_True(t, sa.Group != nil)
rg := sa.Group
require_True(t, rg.node != nil)
n := rg.node
n.Stop()
n.WaitForStop()
// We wait for the monitor to exit, so we can set the flag back manually.
checkFor(t, 5*time.Second, time.Second, func() error {
mset.mu.RLock()
defer mset.mu.RUnlock()
if mset.inMonitor {
return errors.New("waiting for monitor to stop")
}
return nil
})
mset.mu.Lock()
mset.inMonitor = true
mset.mu.Unlock()
// The RAFT node should be closed. Checking health must not change that.
// Simulates a race condition where we're shutting down, but we're still in the stream monitor.
checkNodeIsClosed(sa)
sjs.isStreamHealthy(acc, sa)
checkNodeIsClosed(sa)
err = js.DeleteStream("TEST")
require_NoError(t, err)
waitForNoStreamAssignments()
// Underlying layer would be aware the health check made a copy.
// So we sneakily set these values back, which simulates a race condition where
// the health check is called while the deletion is in progress. This could happen
// depending on how the locks are used.
sjs.mu.Lock()
sjs.cluster.streams = make(map[string]map[string]*streamAssignment)
sjs.cluster.streams[globalAccountName] = make(map[string]*streamAssignment)
sjs.cluster.streams[globalAccountName]["TEST"] = sa
sa.Group.node = n
sjs.mu.Unlock()
// The underlying stream has been deleted. Checking health must not recreate the stream.
checkNodeIsClosed(sa)
sjs.isStreamHealthy(acc, sa)
checkNodeIsClosed(sa)
}
func TestJetStreamClusterStreamHealthCheckMustNotDeleteEarly(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
waitForStreamAssignments := func() {
t.Helper()
checkFor(t, 5*time.Second, time.Second, func() error {
for _, s := range c.servers {
js := s.getJetStream()
js.mu.RLock()
sa := js.streamAssignment(globalAccountName, "TEST")
js.mu.RUnlock()
if sa == nil {
return fmt.Errorf("stream assignment not found on %s", s.Name())
}
}
return nil
})
}
getStreamAssignment := func(rs *Server) (*jetStream, *Account, *streamAssignment, *stream) {
acc, err := rs.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NotNil(t, err)
sjs := rs.getJetStream()
sjs.mu.RLock()
defer sjs.mu.RUnlock()
sas := sjs.cluster.streams[globalAccountName]
require_True(t, sas != nil)
sa := sas["TEST"]
require_True(t, sa != nil)
sa.Created = time.Time{}
return sjs, acc, sa, mset
}
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
waitForStreamAssignments()
// We manually clear the node on the stream.
rs := c.randomNonStreamLeader(globalAccountName, "TEST")
sjs, acc, sa, mset := getStreamAssignment(rs)
mset.mu.Lock()
mset.node = nil
mset.mu.Unlock()
sjs.mu.Lock()
group := sa.Group
if group == nil {
sjs.mu.Unlock()
t.Fatal("sa.Group not initialized")
}
node := group.node
if node == nil {
sjs.mu.Unlock()
t.Fatal("sa.Group.node not initialized")
}
sjs.mu.Unlock()
// The health check gets the Raft node of the assignment and checks it against the
// Raft node of the stream. We simulate a race condition where the stream's Raft node
// is not yet initialized. The health check MUST NOT delete the node.
sjs.isStreamHealthy(acc, sa)
require_Equal(t, node.State(), Follower)
}
func TestJetStreamClusterStreamHealthCheckOnlyReportsSkew(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
waitForStreamAssignments := func() {
t.Helper()
checkFor(t, 5*time.Second, time.Second, func() error {
for _, s := range c.servers {
js := s.getJetStream()
js.mu.RLock()
sa := js.streamAssignment(globalAccountName, "TEST")
js.mu.RUnlock()
if sa == nil {
return fmt.Errorf("stream assignment not found on %s", s.Name())
}
}
return nil
})
}
getStreamAssignment := func(rs *Server) (*jetStream, *Account, *streamAssignment, *stream) {
acc, err := rs.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NotNil(t, err)
sjs := rs.getJetStream()
sjs.mu.RLock()
defer sjs.mu.RUnlock()
sas := sjs.cluster.streams[globalAccountName]
require_True(t, sas != nil)
sa := sas["TEST"]
require_True(t, sa != nil)
sa.Created = time.Time{}
return sjs, acc, sa, mset
}
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
waitForStreamAssignments()
// Confirm the stream and assignment Raft nodes are equal.
rs := c.randomNonStreamLeader(globalAccountName, "TEST")
sjs, acc, sa, mset := getStreamAssignment(rs)
mset.mu.RLock()
msetNode := mset.node
mset.mu.RUnlock()
sjs.mu.Lock()
group := sa.Group
if group == nil {
sjs.mu.Unlock()
t.Fatal("sa.Group not initialized")
}
node := group.node
if node == nil {
sjs.mu.Unlock()
t.Fatal("sa.Group.node not initialized")
}
sjs.mu.Unlock()
require_Equal(t, msetNode, node)
// Simulate stopping and restarting a new instance.
node.Stop()
node.WaitForStop()
node, err = sjs.createRaftGroup(globalAccountName, group, false, FileStorage, pprofLabels{})
require_NoError(t, err)
require_NotEqual(t, node.State(), Closed)
// The health check gets the Raft node of the assignment and checks it against the
// Raft node of the stream. We simulate a race condition where the assignment's Raft node
// is re-newed, but the stream's node is still the old instance.
// The health check MUST NOT delete the node.
require_Error(t, sjs.isStreamHealthy(acc, sa), errors.New("cluster node skew detected"))
require_NotEqual(t, node.State(), Closed)
}
func TestJetStreamClusterStreamHealthCheckStreamCatchup(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
sl := c.streamLeader(globalAccountName, "TEST")
sjs := sl.getJetStream()
acc := sl.globalAccount()
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
sjs.mu.Lock()
sa := sjs.streamAssignment(globalAccountName, "TEST")
sjs.mu.Unlock()
require_NoError(t, sjs.isStreamHealthy(acc, sa))
// Check we can report unhealthy.
n := mset.raftNode().(*raft)
n.Lock()
n.commit = 0
n.Unlock()
require_Error(t, sjs.isStreamHealthy(acc, sa), errors.New("group node unhealthy"))
// Catching up should have precedence.
mset.setCatchingUp()
require_True(t, mset.isCatchingUp())
require_Error(t, sjs.isStreamHealthy(acc, sa), errors.New("stream catching up"))
}
func TestJetStreamClusterConsumerHealthCheckMustNotRecreate(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
waitForConsumerAssignments := func() {
t.Helper()
checkFor(t, 5*time.Second, time.Second, func() error {
for _, s := range c.servers {
if s.getJetStream().consumerAssignment(globalAccountName, "TEST", "CONSUMER") == nil {
return fmt.Errorf("stream assignment not found on %s", s.Name())
}
}
return nil
})
}
waitForNoConsumerAssignments := func() {
t.Helper()
checkFor(t, 5*time.Second, time.Second, func() error {
for _, s := range c.servers {
if s.getJetStream().consumerAssignment(globalAccountName, "TEST", "CONSUMER") != nil {
return fmt.Errorf("stream assignment still available on %s", s.Name())
}
}
return nil
})
}
getConsumerAssignment := func(rs *Server) (*jetStream, *streamAssignment, *consumerAssignment, *stream) {
acc, err := rs.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NotNil(t, err)
sjs := rs.getJetStream()
sjs.mu.RLock()
defer sjs.mu.RUnlock()
sas := sjs.cluster.streams[globalAccountName]
require_True(t, sas != nil)
sa := sas["TEST"]
require_True(t, sa != nil)
ca := sa.consumers["CONSUMER"]
require_True(t, ca != nil)
ca.Created = time.Time{}
return sjs, sa, ca, mset
}
checkNodeIsClosed := func(ca *consumerAssignment) {
t.Helper()
require_True(t, ca.Group != nil)
rg := ca.Group
require_True(t, rg.node != nil)
n := rg.node
require_Equal(t, n.State(), Closed)
}
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
Retention: nats.InterestPolicy, // Replicated consumers by default
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "CONSUMER"})
require_NoError(t, err)
waitForConsumerAssignments()
// We manually stop the RAFT node and ensure it doesn't get restarted.
rs := c.randomNonConsumerLeader(globalAccountName, "TEST", "CONSUMER")
sjs, sa, ca, mset := getConsumerAssignment(rs)
require_True(t, ca.Group != nil)
rg := ca.Group
require_True(t, rg.node != nil)
n := rg.node
n.Stop()
n.WaitForStop()
// The RAFT node should be closed. Checking health must not change that.
// Simulates a race condition where we're shutting down.
checkNodeIsClosed(ca)
require_Error(t, sjs.isConsumerHealthy(mset, "CONSUMER", ca), errors.New("monitor goroutine not running"))
checkNodeIsClosed(ca)
// We create a new RAFT group, the health check should detect this skew.
_, err = sjs.createRaftGroup(globalAccountName, ca.Group, false, MemoryStorage, pprofLabels{})
require_NoError(t, err)
sjs.mu.Lock()
// We set creating to now, since previously it would delete all data but NOT restart if created within <10s.
ca.Created = time.Now()
sjs.mu.Unlock()
require_Error(t, sjs.isConsumerHealthy(mset, "CONSUMER", ca), errors.New("cluster node skew detected"))
err = js.DeleteConsumer("TEST", "CONSUMER")
require_NoError(t, err)
waitForNoConsumerAssignments()
// Underlying layer would be aware the health check made a copy.
// So we sneakily set these values back, which simulates a race condition where
// the health check is called while the deletion is in progress. This could happen
// depending on how the locks are used.
sjs.mu.Lock()
sjs.cluster.streams = make(map[string]map[string]*streamAssignment)
sjs.cluster.streams[globalAccountName] = make(map[string]*streamAssignment)
sjs.cluster.streams[globalAccountName]["TEST"] = sa
ca.Created = time.Time{}
ca.Group.node = n
sjs.mu.Unlock()
// The underlying consumer has been deleted. Checking health must not recreate the consumer.
checkNodeIsClosed(ca)
require_Error(t, sjs.isConsumerHealthy(mset, "CONSUMER", ca), errors.New("consumer not found"))
checkNodeIsClosed(ca)
}
func TestJetStreamClusterConsumerHealthCheckMustNotDeleteEarly(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
waitForConsumerAssignments := func() {
t.Helper()
checkFor(t, 5*time.Second, time.Second, func() error {
for _, s := range c.servers {
if s.getJetStream().consumerAssignment(globalAccountName, "TEST", "CONSUMER") == nil {
return fmt.Errorf("stream assignment not found on %s", s.Name())
}
}
return nil
})
}
getConsumerAssignment := func(rs *Server) (*jetStream, *consumerAssignment, *stream, *consumer) {
acc, err := rs.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NotNil(t, err)
o := mset.lookupConsumer("CONSUMER")
sjs := rs.getJetStream()
sjs.mu.RLock()
defer sjs.mu.RUnlock()
sas := sjs.cluster.streams[globalAccountName]
require_True(t, sas != nil)
sa := sas["TEST"]
require_True(t, sa != nil)
ca := sa.consumers["CONSUMER"]
require_True(t, ca != nil)
ca.Created = time.Time{}
return sjs, ca, mset, o
}
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
Retention: nats.InterestPolicy, // Replicated consumers by default
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "CONSUMER"})
require_NoError(t, err)
waitForConsumerAssignments()
// We manually clear the node on the consumer.
rs := c.randomNonConsumerLeader(globalAccountName, "TEST", "CONSUMER")
sjs, ca, mset, o := getConsumerAssignment(rs)
o.mu.Lock()
o.node = nil
o.mu.Unlock()
sjs.mu.Lock()
group := ca.Group
if group == nil {
sjs.mu.Unlock()
t.Fatal("ca.Group not initialized")
}
node := group.node
if node == nil {
sjs.mu.Unlock()
t.Fatal("ca.Group.node not initialized")
}
sjs.mu.Unlock()
// The health check gets the Raft node of the assignment and checks it against the
// Raft node of the consumer. We simulate a race condition where the consumer's Raft node
// is not yet initialized. The health check MUST NOT delete the node.
sjs.isConsumerHealthy(mset, "CONSUMER", ca)
require_Equal(t, node.State(), Follower)
}
func TestJetStreamClusterConsumerHealthCheckOnlyReportsSkew(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
waitForConsumerAssignments := func() {
t.Helper()
checkFor(t, 5*time.Second, time.Second, func() error {
for _, s := range c.servers {
if s.getJetStream().consumerAssignment(globalAccountName, "TEST", "CONSUMER") == nil {
return fmt.Errorf("stream assignment not found on %s", s.Name())
}
}
return nil
})
}
getConsumerAssignment := func(rs *Server) (*jetStream, *consumerAssignment, *stream, *consumer) {
acc, err := rs.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NotNil(t, err)
o := mset.lookupConsumer("CONSUMER")
sjs := rs.getJetStream()
sjs.mu.RLock()
defer sjs.mu.RUnlock()
sas := sjs.cluster.streams[globalAccountName]
require_True(t, sas != nil)
sa := sas["TEST"]
require_True(t, sa != nil)
ca := sa.consumers["CONSUMER"]
require_True(t, ca != nil)
ca.Created = time.Time{}
return sjs, ca, mset, o
}
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
Retention: nats.InterestPolicy, // Replicated consumers by default
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "CONSUMER"})
require_NoError(t, err)
waitForConsumerAssignments()
// Confirm the consumer and assignment Raft nodes are equal.
rs := c.randomNonConsumerLeader(globalAccountName, "TEST", "CONSUMER")
sjs, ca, mset, o := getConsumerAssignment(rs)
o.mu.RLock()
oNode := o.node
o.mu.RUnlock()
sjs.mu.Lock()
group := ca.Group
if group == nil {
sjs.mu.Unlock()
t.Fatal("ca.Group not initialized")
}
node := group.node
if node == nil {
sjs.mu.Unlock()
t.Fatal("ca.Group.node not initialized")
}
sjs.mu.Unlock()
require_Equal(t, oNode, node)
// Simulate stopping and restarting a new instance.
node.Stop()
node.WaitForStop()
node, err = sjs.createRaftGroup(globalAccountName, group, false, FileStorage, pprofLabels{})
require_NoError(t, err)
require_NotEqual(t, node.State(), Closed)
// The health check gets the Raft node of the assignment and checks it against the
// Raft node of the consumer. We simulate a race condition where the assignment's Raft node
// is re-newed, but the consumer's node is still the old instance.
// The health check MUST NOT delete the node.
require_Error(t, sjs.isConsumerHealthy(mset, "CONSUMER", ca), errors.New("cluster node skew detected"))
require_NotEqual(t, node.State(), Closed)
}
// https://github.com/nats-io/nats-server/issues/7149
func TestJetStreamClusterConsumerHealthCheckDeleted(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 1,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "CONSUMER"})
require_NoError(t, err)
cl := c.consumerLeader(globalAccountName, "TEST", "CONSUMER")
require_NotNil(t, cl)
mset, err := cl.globalAccount().lookupStream("TEST")
require_NoError(t, err)
sjs := cl.getJetStream()
sjs.mu.Lock()
ca := sjs.consumerAssignment(globalAccountName, "TEST", "CONSUMER")
if ca == nil {
sjs.mu.Unlock()
t.Fatal("ca not found")
}
// Reset created time, simulating the consumer existed already for a while.
ca.Created = time.Time{}
sjs.mu.Unlock()
// The health check gathers all assignments and does checking after.
// If the consumer was deleted in the meantime, it should not report an error.
require_NoError(t, js.DeleteConsumer("TEST", "CONSUMER"))
require_Error(t, sjs.isConsumerHealthy(mset, "CONSUMER", ca), errors.New("consumer not found"))
// The health check could run earlier than we're able to create the consumer.
// In that case, wait before erroring.
sjs.mu.Lock()
ca.Created = time.Now()
sjs.mu.Unlock()
require_NoError(t, sjs.isConsumerHealthy(mset, "CONSUMER", ca))
}
func TestJetStreamClusterStreamHealthCheckSurfacesAssignmentErr(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
sl := c.streamLeader(globalAccountName, "TEST")
sjs := sl.getJetStream()
acc := sl.globalAccount()
sjs.mu.Lock()
sa := sjs.streamAssignment(globalAccountName, "TEST")
sjs.mu.Unlock()
require_True(t, sa != nil)
// Baseline: healthy.
require_NoError(t, sjs.isStreamHealthy(acc, sa))
// Persisted assignment-level error must surface via the health check.
wantErr := errors.New("synthetic stream assignment failure")
sjs.mu.Lock()
sa.err = wantErr
sjs.mu.Unlock()
err = sjs.isStreamHealthy(acc, sa)
require_Error(t, err)
require_Contains(t, err.Error(), wantErr.Error())
// Clearing the err brings health back.
sjs.mu.Lock()
sa.err = nil
sjs.mu.Unlock()
require_NoError(t, sjs.isStreamHealthy(acc, sa))
}
func TestJetStreamClusterConsumerHealthCheckSurfacesAssignmentErr(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "CONSUMER"})
require_NoError(t, err)
cl := c.consumerLeader(globalAccountName, "TEST", "CONSUMER")
require_NotNil(t, cl)
mset, err := cl.globalAccount().lookupStream("TEST")
require_NoError(t, err)
sjs := cl.getJetStream()
sjs.mu.Lock()
ca := sjs.consumerAssignment(globalAccountName, "TEST", "CONSUMER")
sjs.mu.Unlock()
require_True(t, ca != nil)
// Baseline: healthy.
require_NoError(t, sjs.isConsumerHealthy(mset, "CONSUMER", ca))
// Persisted assignment-level error must surface via the health check.
wantErr := errors.New("synthetic consumer assignment failure")
sjs.mu.Lock()
ca.err = wantErr
sjs.mu.Unlock()
err = sjs.isConsumerHealthy(mset, "CONSUMER", ca)
require_Error(t, err)
require_Contains(t, err.Error(), wantErr.Error())
// Clearing the err brings health back.
sjs.mu.Lock()
ca.err = nil
sjs.mu.Unlock()
require_NoError(t, sjs.isConsumerHealthy(mset, "CONSUMER", ca))
}
func TestJetStreamClusterStreamHealthCheckRecoversAfterSuccessfulUpdate(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
sl := c.streamLeader(globalAccountName, "TEST")
sjs := sl.getJetStream()
acc := sl.globalAccount()
sjs.mu.Lock()
sa := sjs.streamAssignment(globalAccountName, "TEST")
sjs.mu.Unlock()
require_True(t, sa != nil)
// Plant a transient assignment-level error. Health check must now fail.
wantErr := errors.New("synthetic stream assignment failure")
sjs.mu.Lock()
sa.err = wantErr
sjs.mu.Unlock()
err = sjs.isStreamHealthy(acc, sa)
require_Error(t, err)
require_Contains(t, err.Error(), wantErr.Error())
// A successful update should clear it.
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 3,
})
require_NoError(t, err)
sjs.mu.RLock()
cur := sjs.streamAssignment(globalAccountName, "TEST")
var gotErr error
if cur != nil {
gotErr = cur.err
}
sjs.mu.RUnlock()
require_True(t, cur != nil)
require_NoError(t, gotErr)
require_NoError(t, sjs.isStreamHealthy(acc, cur))
}
func TestJetStreamClusterConsumerHealthCheckRecoversAfterSuccessfulUpdate(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "CONSUMER",
AckPolicy: nats.AckExplicitPolicy,
MaxAckPending: 100,
})
require_NoError(t, err)
cl := c.consumerLeader(globalAccountName, "TEST", "CONSUMER")
require_NotNil(t, cl)
mset, err := cl.globalAccount().lookupStream("TEST")
require_NoError(t, err)
sjs := cl.getJetStream()
sjs.mu.Lock()
ca := sjs.consumerAssignment(globalAccountName, "TEST", "CONSUMER")
sjs.mu.Unlock()
require_True(t, ca != nil)
// Plant a transient assignment-level error. Health check must now fail.
wantErr := errors.New("synthetic consumer assignment failure")
sjs.mu.Lock()
ca.err = wantErr
sjs.mu.Unlock()
err = sjs.isConsumerHealthy(mset, "CONSUMER", ca)
require_Error(t, err)
require_Contains(t, err.Error(), wantErr.Error())
// A successful update should clear it.
_, err = js.UpdateConsumer("TEST", &nats.ConsumerConfig{
Durable: "CONSUMER",
AckPolicy: nats.AckExplicitPolicy,
MaxAckPending: 200,
})
require_NoError(t, err)
sjs.mu.RLock()
cur := sjs.consumerAssignment(globalAccountName, "TEST", "CONSUMER")
var gotErr error
if cur != nil {
gotErr = cur.err
}
sjs.mu.RUnlock()
require_True(t, cur != nil)
require_NoError(t, gotErr)
require_NoError(t, sjs.isConsumerHealthy(mset, "CONSUMER", cur))
}
func TestJetStreamClusterRespectConsumerStartSeq(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
// Create replicated stream.
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
// We could have published messages into the stream that have not yet been applied on the follower.
// If we create a consumer with a starting sequence in the future, we must respect it.
ci, err := js.AddConsumer("TEST", &nats.ConsumerConfig{
DeliverPolicy: nats.DeliverByStartSequencePolicy,
OptStartSeq: 20,
})
require_NoError(t, err)
require_Equal(t, ci.Delivered.Stream, 19)
// Same thing if the first sequence is not 0.
err = js.PurgeStream("TEST", &nats.StreamPurgeRequest{Sequence: 10})
require_NoError(t, err)
// Ensure all servers are up-to-date.
checkFor(t, 2*time.Second, 500*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "TEST")
})
ci, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
DeliverPolicy: nats.DeliverByStartSequencePolicy,
OptStartSeq: 20,
})
require_NoError(t, err)
require_Equal(t, ci.Delivered.Stream, 19)
// Only if we're requested to start at a sequence that's not available anymore
// can we safely move it up. That data is gone already, so can't do anything else.
ci, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
DeliverPolicy: nats.DeliverByStartSequencePolicy,
OptStartSeq: 5,
})
require_NoError(t, err)
require_Equal(t, ci.Delivered.Stream, 9)
}
func TestJetStreamClusterPeerRemoveStreamConsumerDesync(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
// Must have at least one message in the stream.
_, err = js.Publish("foo", nil)
require_NoError(t, err)
sl := c.streamLeader(globalAccountName, "TEST")
acc, err := sl.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
rs := c.randomNonStreamLeader(globalAccountName, "TEST")
peer := rs.Name()
rn := mset.raftNode()
mset.mu.Lock()
esm := encodeStreamMsgAllowCompress("foo", _EMPTY_, nil, nil, mset.clseq, time.Now().UnixNano(), false)
mset.clseq++
mset.mu.Unlock()
// Propose both remove peer and a normal entry within the same append entry.
err = rn.ProposeMulti([]*Entry{
newEntry(EntryRemovePeer, []byte(peer)),
newEntry(EntryNormal, esm),
})
require_NoError(t, err)
// If the previous normal entry was skipped, we'd get a seq mismatch error here.
_, err = js.Publish("foo", nil)
require_NoError(t, err)
// Now check the same but for a consumer.
_, err = js.PullSubscribe("foo", "CONSUMER")
require_NoError(t, err)
cl := c.consumerLeader(globalAccountName, "TEST", "CONSUMER")
acc, err = cl.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err = acc.lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
updateDeliveredBuffer := func() []byte {
var b [4*binary.MaxVarintLen64 + 1]byte
b[0] = byte(updateDeliveredOp)
n := 1
n += binary.PutUvarint(b[n:], 100)
n += binary.PutUvarint(b[n:], 100)
n += binary.PutUvarint(b[n:], 1)
n += binary.PutVarint(b[n:], time.Now().UnixNano())
return b[:n]
}
rs = c.randomNonConsumerLeader(globalAccountName, "TEST", "CONSUMER")
peer = rs.Name()
rn = o.raftNode()
// Propose both remove peer and a normal entry within the same append entry.
err = rn.ProposeMulti([]*Entry{
newEntry(EntryRemovePeer, []byte(peer)),
newEntry(EntryNormal, updateDeliveredBuffer()),
})
require_NoError(t, err)
// Check the normal entry was applied.
checkFor(t, 2*time.Second, 500*time.Millisecond, func() error {
o.mu.Lock()
defer o.mu.Unlock()
cs, err := o.store.State()
if err != nil {
return err
}
if cs.Delivered.Consumer != 100 || cs.Delivered.Stream != 100 {
return fmt.Errorf("expected sequence 100, got: %v", cs.Delivered)
}
return nil
})
}
func TestJetStreamClusterStuckConsumerAfterLeaderChangeWithUnknownDeliveries(t *testing.T) {
testJetStreamClusterStuckConsumerAfterLeaderChangeWithUnknownDeliveries(t, false)
}
func TestJetStreamClusterStuckConsumerV2AfterLeaderChangeWithUnknownDeliveries(t *testing.T) {
testJetStreamClusterStuckConsumerAfterLeaderChangeWithUnknownDeliveries(t, true)
}
func testJetStreamClusterStuckConsumerAfterLeaderChangeWithUnknownDeliveries(t *testing.T, ackV2 bool) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
for _, s := range c.servers {
opts := *s.getOpts()
opts.FeatureFlags = map[string]bool{FeatureFlagJsAckFormatV2: ackV2}
s.setOpts(&opts)
}
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
// Publish some messages into the stream.
for i := 0; i < 3; i++ {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
// Ensure all servers are up-to-date.
checkFor(t, 2*time.Second, 500*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "TEST")
})
sub, err := js.PullSubscribe("foo", "CONSUMER")
require_NoError(t, err)
defer sub.Unsubscribe()
// We only fetch 1 message here, since the condition is hard to trigger otherwise.
// But, we're simulating fetching 3 messages and the consumer leader changing while
// deliveries are happening. This will result in the new consumer leader not knowing
// that the last two messages were also delivered (since we don't wait for quorum before delivering).
msgs, err := sub.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
cl := c.consumerLeader(globalAccountName, "TEST", "CONSUMER")
require_NotNil(t, cl)
mset, err := cl.globalAccount().lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
// The client could send an acknowledgement, while the new consumer leader doesn't know about it
// ever being delivered. It must NOT adjust any state and ignore the request to remain consistent.
o.mu.RLock()
ackReply := o.ackReply(3, 3, 1, 0, 0)
o.mu.RUnlock()
if ackV2 {
require_Equal(t, ackReply, "$JS.ACK._.szMpdrwD.TEST.CONSUMER.1.3.3.0.0")
} else {
require_Equal(t, ackReply, "$JS.ACK.TEST.CONSUMER.1.3.3.0.0")
}
_, err = nc.Request(ackReply, nil, time.Second)
require_Error(t, err, nats.ErrTimeout)
// Acknowledging a message that is known to be delivered is accepted still.
o.mu.RLock()
ackReply = o.ackReply(1, 1, 1, 0, 0)
o.mu.RUnlock()
if ackV2 {
require_Equal(t, ackReply, "$JS.ACK._.szMpdrwD.TEST.CONSUMER.1.1.1.0.0")
} else {
require_Equal(t, ackReply, "$JS.ACK.TEST.CONSUMER.1.1.1.0.0")
}
_, err = nc.Request(ackReply, nil, time.Second)
require_NoError(t, err)
// Check for consistent consumer info.
ci, err := js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_Equal(t, ci.Delivered.Consumer, 1)
require_Equal(t, ci.Delivered.Stream, 1)
require_Equal(t, ci.AckFloor.Consumer, 1)
require_Equal(t, ci.AckFloor.Stream, 1)
// Fetching for new messages MUST return the two messages the new consumer leader didn't
// know were delivered before. If we wouldn't deliver these we'd have a stuck consumer.
msgs, err = sub.Fetch(2)
require_NoError(t, err)
require_Len(t, len(msgs), 2)
for _, msg := range msgs {
require_NoError(t, msg.AckSync())
}
// Check for consistent consumer info.
ci, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_Equal(t, ci.Delivered.Consumer, 3)
require_Equal(t, ci.Delivered.Stream, 3)
require_Equal(t, ci.AckFloor.Consumer, 3)
require_Equal(t, ci.AckFloor.Stream, 3)
}
// This is for when we are still using $SYS for NRG replication but we want to make sure
// we track this in something visible to the end user.
func TestJetStreamClusterAccountStatsForReplicatedStreams(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R5S", 5)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 5,
})
require_NoError(t, err)
// Let's connect to stream leader to make sent messages predictable, otherwise we get those to come up based on routing to stream leader.
s := c.streamLeader(globalAccountName, "TEST")
require_True(t, s != nil)
nc, js = jsClientConnect(t, s)
defer nc.Close()
// NRG traffic can be compressed, so make this unique so we can check stats correctly.
msg := make([]byte, 1024*1024)
crand.Read(msg)
// Publish some messages into the stream.
for i := 0; i < 10; i++ {
_, err = js.Publish("foo", msg)
require_NoError(t, err)
}
time.Sleep(250 * time.Millisecond)
// Now grab the account stats for us and make sure we account for the replicated messages.
// Opts to grab our account.
opts := &AccountStatzOptions{
Accounts: []string{globalAccountName},
}
as, err := s.AccountStatz(opts)
require_NoError(t, err)
require_Equal(t, len(as.Accounts), 1)
accStats := as.Accounts[0]
// We need to account for possibility that the stream create was also on this server, hence the >= vs strict ==.
require_True(t, accStats.Received.Msgs >= 10)
require_True(t, accStats.Received.Bytes >= 1024*1024)
// For sent, we will have 10 pub acks, and then should have 40 extra messages that are sent and accounted for
// during the nrg propsal to the R5 peers.
require_True(t, accStats.Sent.Msgs >= 50)
require_True(t, accStats.Sent.Bytes >= accStats.Received.Bytes*4)
}
func TestJetStreamClusterRecreateConsumerFromMetaSnapshot(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
// Initial setup.
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
_, err = js.Publish("foo", nil)
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "CONSUMER"})
require_NoError(t, err)
// Wait for all servers to be fully up-to-date.
checkFor(t, 2*time.Second, 500*time.Millisecond, func() error {
if err = checkState(t, c, globalAccountName, "TEST"); err != nil {
return err
}
for _, s := range c.servers {
if acc, err := s.lookupAccount(globalAccountName); err != nil {
return err
} else if mset, err := acc.lookupStream("TEST"); err != nil {
return err
} else if o := mset.lookupConsumer("CONSUMER"); o == nil {
return errors.New("consumer doesn't exist")
}
}
return nil
})
// Shutdown a random server.
rs := c.randomServer()
rs.Shutdown()
rs.WaitForShutdown()
// Recreate connection, since we could have shutdown the server we were connected to.
nc.Close()
c.waitOnLeader()
nc, js = jsClientConnect(t, c.randomServer())
defer nc.Close()
// Recreate consumer.
require_NoError(t, js.DeleteConsumer("TEST", "CONSUMER"))
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "CONSUMER"})
require_NoError(t, err)
// Wait for all servers (except for the one that's down) to have recreated the consumer.
var consumerRg string
checkFor(t, 2*time.Second, 500*time.Millisecond, func() error {
consumerRg = _EMPTY_
for _, s := range c.servers {
if s == rs {
continue
}
if acc, err := s.lookupAccount(globalAccountName); err != nil {
return err
} else if mset, err := acc.lookupStream("TEST"); err != nil {
return err
} else if o := mset.lookupConsumer("CONSUMER"); o == nil {
return errors.New("consumer doesn't exist")
} else if ccrg := o.raftNode().Group(); consumerRg == _EMPTY_ {
consumerRg = ccrg
} else if consumerRg != ccrg {
return errors.New("consumer raft groups don't match")
}
}
return nil
})
// Install snapshots on all remaining servers to "hide" the intermediate consumer recreate requests.
for _, s := range c.servers {
if s != rs {
sjs := s.getJetStream()
require_NotNil(t, sjs)
snap, _, _, err := sjs.metaSnapshot()
require_NoError(t, err)
sjs.mu.RLock()
meta := sjs.cluster.meta
sjs.mu.RUnlock()
require_NoError(t, meta.InstallSnapshot(snap, false))
}
}
// Restart the server, it should receive a meta snapshot and recognize the consumer recreation.
rs = c.restartServer(rs)
checkFor(t, 2*time.Second, 500*time.Millisecond, func() error {
consumerRg = _EMPTY_
for _, s := range c.servers {
if acc, err := s.lookupAccount(globalAccountName); err != nil {
return err
} else if mset, err := acc.lookupStream("TEST"); err != nil {
return err
} else if o := mset.lookupConsumer("CONSUMER"); o == nil {
return errors.New("consumer doesn't exist")
} else if rn := o.raftNode(); rn == nil {
return errors.New("consumer raft node doesn't exist")
} else if ccrg := rn.Group(); ccrg == _EMPTY_ {
return errors.New("consumer raft group doesn't exist")
} else if consumerRg == _EMPTY_ {
consumerRg = ccrg
} else if consumerRg != ccrg {
return errors.New("consumer raft groups don't match")
}
}
return nil
})
}
func TestJetStreamClusterUpgradeStreamVersioning(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
ml := c.leader()
sjs := ml.getJetStream()
rn := sjs.getMetaGroup()
acc, err := ml.lookupAccount(globalAccountName)
require_NoError(t, err)
// Create stream config.
cfg, apiErr := ml.checkStreamCfg(&StreamConfig{Name: "TEST", Subjects: []string{"foo"}}, acc, false)
require_True(t, apiErr == nil)
// Create and propose stream assignment.
ci := &ClientInfo{Cluster: "R3S", Account: globalAccountName}
rg, perr := sjs.createGroupForStream(ci, &cfg)
require_True(t, perr == nil)
sa := &streamAssignment{Group: rg, Sync: syncSubjForStream(), Config: &cfg, Client: ci, Created: time.Now().UTC()}
require_NoError(t, rn.Propose(encodeAddStreamAssignment(sa)))
// Wait for the stream assignment to have gone through.
checkFor(t, 2*time.Second, 500*time.Millisecond, func() error {
sjs.mu.RLock()
defer sjs.mu.RUnlock()
if sjs.streamAssignment(globalAccountName, "TEST") == nil {
return errors.New("stream assignment does not exist yet")
}
return nil
})
for _, create := range []bool{true, false} {
title := "create"
if !create {
title = "update"
}
t.Run(title, func(t *testing.T) {
if create {
// Create on 2.11+ should be idempotent with previous create on 2.10-.
mcfg := &StreamConfig{}
setStaticStreamMetadata(mcfg)
si, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Metadata: setDynamicStreamMetadata(mcfg).Metadata,
})
require_NoError(t, err)
deleteDynamicMetadata(si.Config.Metadata)
require_Len(t, len(si.Config.Metadata), 0)
} else {
// Update populates the versioning metadata.
si, err := js.UpdateStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}})
require_NoError(t, err)
require_Len(t, len(si.Config.Metadata), 3)
}
})
}
}
func TestJetStreamClusterUpgradeConsumerVersioning(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Retention: nats.LimitsPolicy,
Replicas: 1,
})
require_NoError(t, err)
ml := c.leader()
sjs := ml.getJetStream()
// Wait for the stream assignment to have gone through.
checkFor(t, 2*time.Second, 500*time.Millisecond, func() error {
sjs.mu.RLock()
defer sjs.mu.RUnlock()
if sjs.streamAssignment(globalAccountName, "TEST") == nil {
return errors.New("stream assignment does not exist yet")
}
return nil
})
rn := sjs.getMetaGroup()
sa := sjs.streamAssignment(globalAccountName, "TEST")
require_NotNil(t, sa)
acc, err := ml.lookupAccount(globalAccountName)
require_NoError(t, err)
// Create consumer config.
cfg := &ConsumerConfig{Durable: "CONSUMER", Name: "CONSUMER"}
streamCfg, ok := sjs.clusterStreamConfig(acc.Name, "TEST")
if !ok {
require_NoError(t, NewJSStreamNotFoundError())
}
selectedLimits, _, _, apiErr := acc.selectLimits(cfg.replicas(&streamCfg))
if apiErr != nil {
require_NoError(t, apiErr)
}
srvLim := &ml.getOpts().JetStreamLimits
apiErr = setConsumerConfigDefaults(cfg, &streamCfg, srvLim, selectedLimits, false)
if apiErr != nil {
require_NoError(t, apiErr)
}
// Create and propose consumer assignment.
ci := &ClientInfo{Cluster: "R3S", Account: globalAccountName}
rg := sjs.cluster.createGroupForConsumer(cfg, sa)
ca := &consumerAssignment{Group: rg, Stream: "TEST", Name: "CONSUMER", Config: cfg, Client: ci, Created: time.Now().UTC()}
require_NoError(t, rn.Propose(encodeAddConsumerAssignment(ca)))
// Wait for the consumer assignment to have gone through.
checkFor(t, 2*time.Second, 500*time.Millisecond, func() error {
sjs.mu.RLock()
defer sjs.mu.RUnlock()
if sjs.consumerAssignment(globalAccountName, "TEST", "CONSUMER") == nil {
return errors.New("consumer assignment does not exist yet")
}
return nil
})
for _, create := range []bool{true, false} {
title := "create"
if !create {
title = "update"
}
t.Run(title, func(t *testing.T) {
createConsumerRequest := func(obsReq CreateConsumerRequest) (*JSApiConsumerInfoResponse, error) {
req, err := json.Marshal(obsReq)
if err != nil {
return nil, err
}
msg, err := nc.Request(fmt.Sprintf(JSApiDurableCreateT, "TEST", "CONSUMER"), req, time.Second)
if err != nil {
return nil, err
}
var resp JSApiConsumerInfoResponse
require_NoError(t, json.Unmarshal(msg.Data, &resp))
if resp.Error != nil {
return nil, resp.Error
}
return &resp, nil
}
if create {
// Create on 2.11+ should be idempotent with previous create on 2.10-.
ncfg := &ConsumerConfig{Durable: "CONSUMER"}
setStaticConsumerMetadata(ncfg)
obsReq := CreateConsumerRequest{
Stream: "TEST",
Config: *setDynamicConsumerMetadata(ncfg),
Action: ActionCreate,
}
resp, err := createConsumerRequest(obsReq)
require_NoError(t, err)
deleteDynamicMetadata(resp.Config.Metadata)
require_Len(t, len(resp.Config.Metadata), 0)
} else {
// Update populates the versioning metadata.
obsReq := CreateConsumerRequest{
Stream: "TEST",
Config: ConsumerConfig{Durable: "CONSUMER"},
Action: ActionUpdate,
}
resp, err := createConsumerRequest(obsReq)
require_NoError(t, err)
require_Len(t, len(resp.Config.Metadata), 3)
}
})
}
}
func TestJetStreamClusterInterestPolicyAckAll(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Retention: nats.InterestPolicy,
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "CONSUMER",
AckPolicy: nats.AckAllPolicy,
})
require_NoError(t, err)
for i := 0; i < 100; i++ {
_, err = js.Publish("foo", []byte("ok"))
require_NoError(t, err)
}
expectedStreamMsgs := func(msgs uint64) {
t.Helper()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
si, err := js.StreamInfo("TEST")
if err != nil {
return err
}
if si.State.Msgs != msgs {
return fmt.Errorf("require uint64 equal, but got: %d != %d", si.State.Msgs, msgs)
}
return nil
})
}
expectedStreamMsgs(100)
for _, s := range c.servers {
acc, err := s.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
o.mu.Lock()
// Ensure o.checkStateForInterestStream can't hide that the issue happened.
o.chkflr = 1000
o.mu.Unlock()
}
sub, err := js.PullSubscribe("foo", "CONSUMER")
require_NoError(t, err)
msgs, err := sub.Fetch(50)
require_NoError(t, err)
require_True(t, len(msgs) == 50)
require_NoError(t, msgs[49].AckSync())
expectedStreamMsgs(50)
}
func TestJetStreamClusterPreserveRedeliveredWithLaggingStream(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Retention: nats.LimitsPolicy,
Replicas: 3,
})
require_NoError(t, err)
_, err = js.Publish("foo", nil)
require_NoError(t, err)
sub, err := js.PullSubscribe("foo", "CONSUMER", nats.AckWait(500*time.Millisecond))
require_NoError(t, err)
defer sub.Unsubscribe()
msgs, err := sub.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_NoError(t, msgs[0].AckSync())
rsf := c.randomNonStreamLeader(globalAccountName, "TEST")
acc, err := rsf.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
require_NoError(t, mset.raftNode().PauseApply())
_, err = js.Publish("foo", nil)
require_NoError(t, err)
// Move consumer leader to equal stream leader.
sl := c.streamLeader(globalAccountName, "TEST")
req := JSApiLeaderStepdownRequest{Placement: &Placement{Preferred: sl.Name()}}
data, err := json.Marshal(req)
require_NoError(t, err)
_, err = nc.Request(fmt.Sprintf(JSApiConsumerLeaderStepDownT, "TEST", "CONSUMER"), data, time.Second)
require_NoError(t, err)
c.waitOnConsumerLeader(globalAccountName, "TEST", "CONSUMER")
require_Equal(t, c.consumerLeader(globalAccountName, "TEST", "CONSUMER").Name(), sl.Name())
// Get the message to be stored in redelivered state.
msgs, err = sub.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
meta, err := msgs[0].Metadata()
require_NoError(t, err)
require_Equal(t, meta.NumDelivered, 1)
msgs, err = sub.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
meta, err = msgs[0].Metadata()
require_NoError(t, err)
require_Equal(t, meta.NumDelivered, 2)
// Move consumer leader to a different server.
req = JSApiLeaderStepdownRequest{Placement: &Placement{Preferred: rsf.Name()}}
data, err = json.Marshal(req)
require_NoError(t, err)
_, err = nc.Request(fmt.Sprintf(JSApiConsumerLeaderStepDownT, "TEST", "CONSUMER"), data, time.Second)
require_NoError(t, err)
c.waitOnConsumerLeader(globalAccountName, "TEST", "CONSUMER")
require_Equal(t, c.consumerLeader(globalAccountName, "TEST", "CONSUMER").Name(), rsf.Name())
// Confirming the stream contents of the consumer leader don't contain the message yet.
msgs, err = sub.Fetch(1, nats.MaxWait(time.Second))
require_Error(t, err, nats.ErrTimeout)
require_Len(t, len(msgs), 0)
// Now let it apply the missing message.
mset.raftNode().ResumeApply()
// Should preserve delivered state properly, otherwise the client would observe NumDelivered rolled back.
msgs, err = sub.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
meta, err = msgs[0].Metadata()
require_NoError(t, err)
require_Equal(t, meta.NumDelivered, 3)
}
func TestJetStreamClusterInvalidJSACKOverRoute(t *testing.T) {
test := func(t *testing.T, stream, consumer, subject string) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: stream,
Subjects: []string{"foo.>"},
Retention: nats.LimitsPolicy,
Replicas: 3,
})
require_NoError(t, err)
_, err = js.AddConsumer(stream, &nats.ConsumerConfig{
Durable: consumer,
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
// Reconnect to consumer follower.
nc.Close()
rs := c.randomNonConsumerLeader(globalAccountName, stream, consumer)
nc, js = jsClientConnect(t, rs)
defer nc.Close()
_, err = js.Publish(subject, nil)
require_NoError(t, err)
checkFor(t, time.Second, 100*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, stream)
})
sub, err := js.PullSubscribe("foo.>", consumer)
require_NoError(t, err)
defer sub.Unsubscribe()
// The message went over a route and should have the correct subject, and working ACK.
msgs, err := sub.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_Equal(t, msgs[0].Subject, subject)
require_NoError(t, msgs[0].AckSync())
}
for _, s := range []string{"StreamNoAt", "StreamWith@At"} {
for _, c := range []string{"ConsumerNoAt", "ConsumerWith@At"} {
for _, subject := range []string{"foo.no.at", "foo.with@at"} {
t.Run(fmt.Sprintf("%s/%s/%s", s, c, subject), func(t *testing.T) {
test(t, s, c, subject)
})
}
}
}
}
func TestJetStreamClusterConsumerOnlyDeliverMsgAfterQuorum(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Retention: nats.LimitsPolicy,
Replicas: 3,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "CONSUMER",
AckPolicy: nats.AckExplicitPolicy,
Replicas: 3,
AckWait: 2 * time.Second,
})
require_NoError(t, err)
_, err = js.Publish("foo", nil)
require_NoError(t, err)
checkFor(t, time.Second, 100*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "TEST")
})
cl := c.consumerLeader(globalAccountName, "TEST", "CONSUMER")
acc, err := cl.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
rn := o.raftNode().(*raft)
// Force the leader to not be able to make proposals.
rn.Lock()
rn.werr = errors.New("block proposals")
rn.Unlock()
sub, err := js.PullSubscribe("foo", "CONSUMER")
require_NoError(t, err)
defer sub.Unsubscribe()
// We must only receive a message AFTER quorum was met for updating delivered state.
// This should time out since proposals are blocked.
msgs, err := sub.Fetch(1, nats.MaxWait(2*time.Second))
require_Error(t, err, nats.ErrTimeout)
require_Len(t, len(msgs), 0)
// Allow proposals to be made again.
rn.Lock()
rn.werr = nil
rn.Unlock()
// Now it should pass.
msgs, err = sub.Fetch(1, nats.MaxWait(2*time.Second))
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_NoError(t, msgs[0].AckSync())
}
func TestJetStreamClusterConsumerResetPendingDeliveriesOnMaxAckPendingUpdate(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Retention: nats.LimitsPolicy,
Replicas: 3,
})
require_NoError(t, err)
cfg := &nats.ConsumerConfig{
Durable: "CONSUMER",
AckPolicy: nats.AckExplicitPolicy,
Replicas: 3,
AckWait: 2 * time.Second,
MaxAckPending: 100,
}
_, err = js.AddConsumer("TEST", cfg)
require_NoError(t, err)
cl := c.consumerLeader(globalAccountName, "TEST", "CONSUMER")
acc, err := cl.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
o.mu.Lock()
o.pendingDeliveries = map[uint64]*jsPubMsg{0: getJSPubMsgFromPool()}
o.mu.Unlock()
// Increasing does not reset pending deliveries.
cfg.MaxAckPending++
_, err = js.UpdateConsumer("TEST", cfg)
require_NoError(t, err)
o.mu.Lock()
l := len(o.pendingDeliveries)
o.mu.Unlock()
require_Equal(t, l, 1)
// Decreasing does reset pending deliveries, so we can shrink the map.
cfg.MaxAckPending--
_, err = js.UpdateConsumer("TEST", cfg)
require_NoError(t, err)
o.mu.Lock()
l = len(o.pendingDeliveries)
o.mu.Unlock()
require_Equal(t, l, 0)
}
func TestJetStreamClusterConsumerActiveAfterDidNotDeliverOverRoute(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
pubAck, err := js.Publish("foo", nil)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "CONSUMER",
Replicas: 3,
DeliverSubject: "deliver_subject",
})
require_NoError(t, err)
ci, err := js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_False(t, ci.PushBound)
cl := c.consumerLeader(globalAccountName, "TEST", "CONSUMER")
rs := c.randomNonConsumerLeader(globalAccountName, "TEST", "CONSUMER")
ncRs, jsRs := jsClientConnect(t, rs)
defer ncRs.Close()
// Just noop callback.
cb := func(msg *nats.Msg) {}
_, err = jsRs.Subscribe("foo", cb, nats.Bind("TEST", "CONSUMER"))
require_NoError(t, err)
// Eventually the sub should be known to the leader, and report active/bound.
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
ci, err = js.ConsumerInfo("TEST", "CONSUMER")
if err != nil {
return err
}
if !ci.PushBound {
return errors.New("push not bound")
}
return nil
})
// Simulate failed delivery over route connection.
acc, err := cl.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
o.didNotDeliver(1, "foo")
// Consumer should still be active/bound.
ci, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_True(t, ci.PushBound)
}
func TestJetStreamClusterOfflineR1StreamDenyUpdate(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 1,
}
_, err := js.AddStream(cfg)
require_NoError(t, err)
// Stop current R1 stream leader.
sl := c.streamLeader(globalAccountName, "TEST")
sl.Shutdown()
nc.Close()
nc, js = jsClientConnect(t, c.randomServer())
defer nc.Close()
// Wait for meta leader, so we can send an update.
c.waitOnLeader()
_, err = js.StreamInfo("TEST")
require_Error(t, err, NewJSStreamOfflineError())
cfg.Replicas = 3
_, err = js.UpdateStream(cfg)
require_Error(t, err, NewJSStreamOfflineError())
}
func TestJetStreamClusterOfflineR1ConsumerDenyUpdate(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
cfg := &nats.ConsumerConfig{
Durable: "CONSUMER",
Replicas: 1,
}
_, err = js.AddConsumer("TEST", cfg)
require_NoError(t, err)
// Stop current R1 consumer leader.
cl := c.consumerLeader(globalAccountName, "TEST", "CONSUMER")
cl.Shutdown()
nc.Close()
nc, js = jsClientConnect(t, c.randomServer())
defer nc.Close()
// Wait for meta leader, so we can send an update.
c.waitOnLeader()
_, err = js.ConsumerInfo("TEST", "CONSUMER")
require_Error(t, err, NewJSConsumerOfflineError())
cfg.Replicas = 3
_, err = js.UpdateConsumer("TEST", cfg)
require_Error(t, err, NewJSConsumerOfflineError())
}
func TestJetStreamClusterSnapshotStreamAssetOnShutdown(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
var sds []string
for _, s := range c.servers {
sds = append(sds, s.StoreDir())
}
for _, sd := range sds {
matches, err := filepath.Glob(filepath.Join(sd, "$SYS", "_js_", "*", snapshotsDir, "*"))
require_NoError(t, err)
require_True(t, len(matches) > 0)
for _, match := range matches {
require_NoError(t, os.RemoveAll(match))
}
}
// Publish, so we have something new to snapshot.
_, err = js.Publish("foo", nil)
require_NoError(t, err)
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "TEST")
})
// Shutdown servers, and check if all made stream snapshots.
for _, s := range c.servers {
s.Shutdown()
}
for _, sd := range sds {
matches, err := filepath.Glob(filepath.Join(sd, "$SYS", "_js_", "*", snapshotsDir))
require_NoError(t, err)
// Matches _meta_ and stream raft groups.
require_Len(t, len(matches), 2)
var foundStream bool
for _, match := range matches {
if !strings.Contains(match, "S-R3F") {
continue
}
foundStream = true
dirs, err := os.ReadDir(match)
require_NoError(t, err)
if len(dirs) != 1 {
t.Errorf("Missing snapshot for %s", match)
}
}
require_True(t, foundStream)
}
}
func TestJetStreamClusterDontReviveRemovedStream(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
}
_, err := js.AddStream(cfg)
require_NoError(t, err)
// Wait for stream to be assigned on all servers.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
for _, s := range c.servers {
if !s.JetStreamIsStreamAssigned(globalAccountName, "TEST") {
return fmt.Errorf("stream not yet assigned on %s", s.Name())
}
}
return nil
})
sl := c.streamLeader(globalAccountName, "TEST")
rs := c.randomNonStreamLeader(globalAccountName, "TEST")
mset, err := rs.globalAccount().lookupStream("TEST")
require_NoError(t, err)
// Simulate the stream being scaled down, quickly after it was scaled up.
cfg.Replicas = 1
_, err = js.UpdateStream(cfg)
require_NoError(t, err)
// Wait for the stream to not be assigned on the servers (except the leader).
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
for _, s := range c.servers {
if s == sl {
continue
}
if s.JetStreamIsStreamAssigned(globalAccountName, "TEST") {
return fmt.Errorf("stream still assigned on %s", s.Name())
}
}
if !mset.closed.Load() {
return fmt.Errorf("stream not closed yet")
}
return nil
})
// Simulating the stream was catching up and is resetting after timing out.
require_True(t, mset.resetClusteredState(nil))
// Allow for some time here, mset.resetClusteredState might
// spin up a goroutine if it's resetting the stream.
// Confirm the stream is not revived.
time.Sleep(200 * time.Millisecond)
_, err = rs.globalAccount().lookupStream("TEST")
require_Error(t, err, ErrJetStreamStreamNotFound)
}
func TestJetStreamClusterCreateR3StreamWithOfflineNodes(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
ml := c.leader()
nc, js := jsClientConnect(t, ml)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "FOO", Replicas: 3})
require_NoError(t, err)
// Shutdown one server, we'll still have quorum.
rs1 := c.randomNonLeader()
rs1.Shutdown()
_, err = js.AddStream(&nats.StreamConfig{Name: "BAR", Replicas: 3})
require_NoError(t, err)
// While we still have quorum, publish a few messages to the streams.
// The offline server will need to both assign the stream and catchup when it comes back.
for _, subj := range []string{"FOO", "BAR"} {
_, err = js.Publish(subj, nil)
require_NoError(t, err)
}
// Shutdown one more server, only one server left that can't reach quorum on its own.
rs2 := c.randomNonLeader()
rs2.Shutdown()
_, err = js.AddStream(&nats.StreamConfig{Name: "BAZ", Replicas: 3})
require_Contains(t, err.Error(), "no suitable peers for placement", "peer offline")
// Restart offline servers and ensure all comes back up.
c.restartServer(rs1)
c.restartServer(rs2)
c.waitOnLeader()
c.waitOnStreamLeader(globalAccountName, "FOO")
c.waitOnStreamLeader(globalAccountName, "BAR")
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
if err := checkState(t, c, globalAccountName, "FOO"); err != nil {
return err
}
if err := checkState(t, c, globalAccountName, "BAR"); err != nil {
return err
}
return nil
})
}
func TestJetStreamClusterCreateEphemeralConsumerWithOfflineNodes(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
ml := c.leader()
nc, js := jsClientConnect(t, ml)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Replicas: 3})
require_NoError(t, err)
// Shutdown a random server.
c.randomNonLeader().Shutdown()
for range 10 {
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{})
require_NoError(t, err)
}
}
func TestJetStreamClusterSetPreferredToOnlineNode(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
// Shutdown one random server.
ml := c.leader()
rs := c.randomNonLeader()
js := rs.getJetStream()
cc := js.cluster
require_NotNil(t, cc)
meta := cc.meta
require_NotNil(t, meta)
rsid := meta.ID()
rs.Shutdown()
cfg := &StreamConfig{Storage: FileStorage, Replicas: 3}
js = ml.getJetStream()
cc = js.cluster
require_NotNil(t, cc)
peers, err := cc.selectPeerGroup(3, "R3S", cfg, nil, 0, nil)
// Need explicit nil-check.
if err != nil {
require_NoError(t, err)
}
require_Len(t, len(peers), 3)
// Should prefer online peers when selecting preferred.
rg := &raftGroup{Name: groupNameForStream(peers, cfg.Storage), Storage: cfg.Storage, Peers: peers, Cluster: "R3S"}
rg.setPreferred(ml)
require_NotEqual(t, rg.Preferred, rsid)
}
func TestJetStreamClusterAsyncFlushBasics(t *testing.T) {
test := func(t *testing.T, syncAlways bool) {
supportsAsync := !syncAlways
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
for _, s := range c.servers {
s.optsMu.Lock()
s.opts.SyncAlways = syncAlways
s.optsMu.Unlock()
}
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
var s *Server
checkStoreIsAsync := func(expectAsync bool) {
t.Helper()
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
fs := mset.Store().(*fileStore)
fs.mu.RLock()
lmb := fs.lmb
asyncFlush := fs.fcfg.AsyncFlush
fip := fs.fip
fs.mu.RUnlock()
require_Equal(t, asyncFlush, expectAsync)
require_Equal(t, fip, !expectAsync)
require_NotNil(t, lmb)
lmb.mu.RLock()
flusher := lmb.flusher
lmb.mu.RUnlock()
if expectAsync {
if !flusher {
t.Fatal("flusher not initialized")
} else if !asyncFlush {
t.Fatal("async flush config not set")
}
} else {
if flusher {
t.Fatal("flusher still initialized")
} else if asyncFlush {
t.Fatal("async flush config not reset")
}
}
}
cfg := &StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: FileStorage,
Replicas: 1,
}
// Test disabled async flush on create.
_, err := jsStreamCreate(t, nc, cfg)
require_NoError(t, err)
s = c.streamLeader(globalAccountName, "TEST")
_, err = js.Publish("foo", nil)
require_NoError(t, err)
checkStoreIsAsync(false)
// Enabling async flush.
cfg.Replicas = 3
_, err = jsStreamUpdate(t, nc, cfg)
require_NoError(t, err)
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "TEST")
})
_, err = js.Publish("foo", nil)
require_NoError(t, err)
checkStoreIsAsync(supportsAsync)
// Disabling async flush.
cfg.Replicas = 1
_, err = jsStreamUpdate(t, nc, cfg)
require_NoError(t, err)
_, err = js.Publish("foo", nil)
require_NoError(t, err)
checkStoreIsAsync(false)
// Test async flush on create.
require_NoError(t, js.DeleteStream("TEST"))
cfg.Replicas = 3
_, err = jsStreamCreate(t, nc, cfg)
require_NoError(t, err)
s = c.streamLeader(globalAccountName, "TEST")
_, err = js.Publish("foo", nil)
require_NoError(t, err)
checkStoreIsAsync(supportsAsync)
}
t.Run("Default", func(t *testing.T) { test(t, false) })
t.Run("SyncAlways", func(t *testing.T) { test(t, true) })
}
func TestJetStreamClusterAsyncFlushFileStoreFlushOnSnapshot(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: FileStorage,
Replicas: 3,
})
require_NoError(t, err)
// Now shutdown flusher and wait for it to be closed,
// we'll not flush published messages automatically anymore.
sl := c.streamLeader(globalAccountName, "TEST")
mset, err := sl.globalAccount().lookupStream("TEST")
require_NoError(t, err)
fs := mset.Store().(*fileStore)
fs.mu.RLock()
lmb := fs.lmb
fs.mu.RUnlock()
require_NotNil(t, lmb)
lmb.mu.Lock()
if lmb.qch != nil {
close(lmb.qch)
lmb.qch = nil
}
lmb.mu.Unlock()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
lmb.mu.RLock()
defer lmb.mu.RUnlock()
if lmb.flusher {
return errors.New("flusher still active")
}
return nil
})
// Get the highest applied count from snapshot before below publish.
n := mset.raftNode().(*raft)
_, _, previousApplied := n.Progress()
// Confirm no pending writes.
require_Equal(t, lmb.pendingWriteSize(), 0)
// Publishing a message will still work, because writes are async.
_, err = js.Publish("foo", nil)
require_NoError(t, err)
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "TEST")
})
// Confirm above write is pending.
require_Equal(t, lmb.pendingWriteSize(), 33)
// Make the upper layer snapshot by sending leader change signal.
// It doesn't matter that we're already leader, it still gets handled.
// Previously this used the mqch, but that now only snapshots on shutdown.
n.leadc <- true
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
n.Lock()
snap, err := n.loadLastSnapshot()
n.Unlock()
if err != nil {
return err
}
if snap.lastIndex <= previousApplied {
return fmt.Errorf("snapshot still at lastIndex=%d, expected=%d", snap.lastIndex, previousApplied)
}
return nil
})
// Confirm the write is flushed as a result of the snapshot.
require_Equal(t, lmb.pendingWriteSize(), 0)
}
func TestJetStreamClusterScheduledDelayedMessage(t *testing.T) {
for _, replicas := range []int{1, 3} {
for _, storage := range []StorageType{FileStorage, MemoryStorage} {
t.Run(fmt.Sprintf("R%d/%s", replicas, storage), func(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "SchedulesDisabled",
Subjects: []string{"disabled"},
Storage: storage,
Replicas: replicas,
AllowMsgSchedules: false,
})
require_NoError(t, err)
schedulePattern := "@at 1970-01-01T00:00:00Z"
m := nats.NewMsg("disabled")
m.Header.Set("Nats-Schedule", schedulePattern) // Needs to be valid, but is not used.
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageSchedulesDisabledError())
m = nats.NewMsg("disabled")
m.Header.Set("Nats-Schedule", "disabled")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageSchedulesDisabledError())
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "SchedulesEnabledNoTtl",
Subjects: []string{"disabled.ttl"},
Storage: storage,
Replicas: replicas,
AllowMsgSchedules: true,
})
require_NoError(t, err)
m = nats.NewMsg("disabled.ttl")
m.Header.Set("Nats-Schedule", schedulePattern) // Needs to be valid, but is not used.
m.Header.Set("Nats-Schedule-TTL", "1s")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageTTLDisabledError())
cfg := &StreamConfig{
Name: "SchedulesEnabled",
Subjects: []string{"foo.*"},
Storage: storage,
Replicas: replicas,
AllowMsgSchedules: true,
AllowMsgTTL: true,
}
_, err = jsStreamCreate(t, nc, cfg)
require_NoError(t, err)
m = nats.NewMsg("foo.invalid")
m.Header.Set("Nats-Schedule", "invalid")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageSchedulesPatternInvalidError())
// Unknown time zone must be reported as time-zone-invalid, not pattern-invalid.
m = nats.NewMsg("foo.invalid")
m.Header.Set("Nats-Schedule", "0 * * * * *")
m.Header.Set("Nats-Schedule-Time-Zone", "Not/A/Zone")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageSchedulesTimeZoneInvalidError())
// An empty time zone is also invalid.
m.Header.Set("Nats-Schedule-Time-Zone", "")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageSchedulesTimeZoneInvalidError())
// A valid time zone with an invalid pattern still surfaces as pattern-invalid.
m = nats.NewMsg("foo.invalid")
m.Header.Set("Nats-Schedule", "invalid")
m.Header.Set("Nats-Schedule-Time-Zone", "UTC")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageSchedulesPatternInvalidError())
m = nats.NewMsg("foo.invalid")
m.Header.Set("Nats-Schedule", schedulePattern)
m.Header.Set("Nats-Schedule-Target", "not.matching")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageSchedulesTargetInvalidError())
m = nats.NewMsg("foo.invalid")
m.Header.Set("Nats-Schedule", schedulePattern)
m.Header.Set("Nats-Schedule-Target", "foo.*") // Must be literal.
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageSchedulesTargetInvalidError())
m = nats.NewMsg("foo.invalid")
m.Header.Set("Nats-Schedule", schedulePattern)
m.Header.Set("Nats-Schedule-Target", "foo.invalid") // Can't equal the publish subject.
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageSchedulesTargetInvalidError())
m = nats.NewMsg("foo.invalid")
m.Header.Set("Nats-Schedule", schedulePattern)
m.Header.Set("Nats-Schedule-Target", "foo.publish")
m.Header.Set("Nats-Schedule-TTL", "invalid")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageSchedulesTTLInvalidError())
m = nats.NewMsg("foo.invalid")
m.Header.Set("Nats-Schedule", schedulePattern)
m.Header.Set("Nats-Schedule-Target", "foo.publish")
m.Header.Set("Nats-Schedule-TTL", "1s")
m.Header.Set("Nats-Rollup", "all")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageSchedulesRollupInvalidError())
// Schedule with a delay that takes a very long time.
schedule := time.Now().Add(time.Hour).Format(time.RFC3339Nano)
schedulePattern = fmt.Sprintf("@at %s", schedule)
m = nats.NewMsg("foo.schedule")
m.Header.Set("Nats-Schedule", schedulePattern)
m.Header.Set("Nats-Schedule-Target", "foo.publish")
m.Header.Set("Nats-Schedule-TTL", "1s")
m.Header.Set("Nats-Rollup", "sub")
pubAck, err := js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
// Schedule where the rollup is missing, automatically gets the rollup added.
// Also, will rollup the previous message for this schedule.
schedule = time.Now().Add(time.Second).Format(time.RFC3339Nano)
schedulePattern = fmt.Sprintf("@at %s", schedule)
m = nats.NewMsg("foo.schedule")
m.Data = []byte("hello")
m.Header.Set("Nats-Schedule", schedulePattern)
m.Header.Set("Nats-Schedule-Target", "foo.publish")
m.Header.Set("Nats-Schedule-TTL", "1s")
// Add a bunch of headers that will be stripped on the scheduled message.
m.Header.Set("Nats-Expected-Stream", "SchedulesEnabled")
m.Header.Set("Nats-Expected-Last-Sequence", "1")
m.Header.Set("Nats-Expected-Last-Subject-Sequence", "1")
m.Header.Set("Nats-Msg-Id", "X")
m.Header.Set("Nats-TTL", "60s")
m.Header.Set("Header", "Value")
pubAck, err = js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 2)
sl := c.streamLeader(globalAccountName, "SchedulesEnabled")
mset, err := sl.globalAccount().lookupStream("SchedulesEnabled")
require_NoError(t, err)
// Only one schedule exists.
state := mset.state()
require_Equal(t, state.LastSeq, 2)
require_Equal(t, state.Msgs, 1)
// Waiting for the delayed message to be published.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
state = mset.state()
if state.LastSeq != 3 {
return fmt.Errorf("expected last seq 3, got %d", state.LastSeq)
} else if state.Msgs != 1 {
return fmt.Errorf("expected 1 msg, got %d", state.Msgs)
}
return nil
})
// Confirm the scheduled message has the correct data.
rsm, err := js.GetLastMsg("SchedulesEnabled", "foo.publish")
require_NoError(t, err)
require_Equal(t, rsm.Sequence, 3)
require_True(t, bytes.Equal(rsm.Data, []byte("hello")))
require_Len(t, len(rsm.Header), 4)
require_Equal(t, rsm.Header.Get("Nats-Scheduler"), "foo.schedule")
require_Equal(t, rsm.Header.Get("Nats-Schedule-Next"), "purge")
require_Equal(t, rsm.Header.Get("Nats-TTL"), "1s")
require_Equal(t, rsm.Header.Get("Header"), "Value")
// Waiting for the delayed message to age out due to its TTL.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
state = mset.state()
if state.FirstSeq != 4 {
return fmt.Errorf("expected first seq 4, got %d", state.FirstSeq)
} else if state.LastSeq != 3 {
return fmt.Errorf("expected last seq 3, got %d", state.LastSeq)
} else if state.Msgs != 0 {
return fmt.Errorf("expected no messages, got %d", state.Msgs)
}
return nil
})
// Servers should be synced.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "SchedulesEnabled")
})
cfg.AllowMsgSchedules = false
_, err = jsStreamUpdate(t, nc, cfg)
require_Error(t, err, NewJSStreamInvalidConfigError(fmt.Errorf("message schedules can not be disabled")))
})
}
}
}
func TestJetStreamClusterScheduledMessageSubjectSourcing(t *testing.T) {
for _, replicas := range []int{1, 3} {
for _, storage := range []StorageType{FileStorage, MemoryStorage} {
t.Run(fmt.Sprintf("R%d/%s", replicas, storage), func(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
cfg := &StreamConfig{
Name: "SchedulesEnabled",
Subjects: []string{"foo.*"},
Storage: storage,
Replicas: replicas,
AllowMsgSchedules: true,
AllowMsgTTL: true,
}
_, err := jsStreamCreate(t, nc, cfg)
require_NoError(t, err)
m := nats.NewMsg("foo.data")
m.Header.Set("Header", "Value")
m.Data = []byte("data")
pubAck, err := js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
m = nats.NewMsg("foo.schedule")
m.Header.Set("Nats-Schedule", "@at 1970-01-01T00:00:00Z")
m.Header.Set("Nats-Schedule-Target", "foo.publish")
// Invalid sources include if the subject:
// - matches the schedule/target subject
// - contains wildcard/is not literal
for _, src := range []string{"foo.schedule", "foo.publish", "foo.*", "foo.>"} {
m.Header.Set("Nats-Schedule-Source", src)
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageSchedulesSourceInvalidError())
}
// Now publish using a correct source subject.
m.Header.Set("Nats-Schedule-Source", "foo.data")
pubAck, err = js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 2)
sl := c.streamLeader(globalAccountName, "SchedulesEnabled")
mset, err := sl.globalAccount().lookupStream("SchedulesEnabled")
require_NoError(t, err)
state := mset.state()
require_Equal(t, state.LastSeq, 2)
require_Equal(t, state.Msgs, 2)
// Waiting for the delayed message to be published.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
state = mset.state()
if state.LastSeq != 3 {
return fmt.Errorf("expected last seq 3, got %d", state.LastSeq)
} else if state.Msgs != 2 {
// One is the scheduled message, one is the sourced message.
return fmt.Errorf("expected 2 msgs, got %d", state.Msgs)
}
return nil
})
// Confirm the scheduled message has the correct data.
rsm, err := js.GetLastMsg("SchedulesEnabled", "foo.publish")
require_NoError(t, err)
require_Equal(t, rsm.Sequence, 3)
require_True(t, bytes.Equal(rsm.Data, []byte("data")))
require_Len(t, len(rsm.Header), 3)
require_Equal(t, rsm.Header.Get("Nats-Scheduler"), "foo.schedule")
require_Equal(t, rsm.Header.Get("Nats-Schedule-Next"), "purge")
require_Equal(t, rsm.Header.Get("Header"), "Value")
// Servers should be synced.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "SchedulesEnabled")
})
})
}
}
}
func TestJetStreamClusterScheduledDelayedMessageReversedHeaderOrder(t *testing.T) {
for _, replicas := range []int{1, 3} {
for _, storage := range []StorageType{FileStorage, MemoryStorage} {
t.Run(fmt.Sprintf("R%d/%s", replicas, storage), func(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, _ := jsClientConnect(t, c.randomServer())
defer nc.Close()
cfg := &StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
Storage: storage,
Replicas: replicas,
AllowMsgSchedules: true,
AllowMsgTTL: true,
}
_, err := jsStreamCreate(t, nc, cfg)
require_NoError(t, err)
sl := c.streamLeader(globalAccountName, "TEST")
gacc := sl.globalAccount()
// The Nats-Schedule headers share a common prefix, so make sure if these are ordered differently
// we can still properly schedule a message.
hdr := genHeader(nil, "Nats-Schedule-Target", "foo.publish")
hdr = genHeader(hdr, "Nats-Schedule", "@at 1970-01-01T00:00:00Z")
require_NoError(t, sl.sendInternalAccountMsgWithReply(gacc, "foo.schedule", _EMPTY_, hdr, nil, false))
mset, err := gacc.lookupStream("TEST")
require_NoError(t, err)
// Waiting for the delayed message to be published.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
state := mset.state()
if state.LastSeq != 2 {
return fmt.Errorf("expected last seq 2, got %d", state.LastSeq)
} else if state.Msgs != 1 {
return fmt.Errorf("expected 1 msg, got %d", state.Msgs)
}
return nil
})
})
}
}
}
func TestJetStreamClusterScheduledIntervalMessage(t *testing.T) {
for _, replicas := range []int{1, 3} {
for _, storage := range []StorageType{FileStorage, MemoryStorage} {
t.Run(fmt.Sprintf("R%d/%s", replicas, storage), func(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
cfg := &StreamConfig{
Name: "SchedulesEnabled",
Subjects: []string{"foo.*"},
Storage: storage,
Replicas: replicas,
AllowMsgSchedules: true,
}
_, err := jsStreamCreate(t, nc, cfg)
require_NoError(t, err)
// Schedule with an interval that triggers often.
m := nats.NewMsg("foo.schedule")
m.Header.Set("Nats-Schedule", "@every 1s")
m.Header.Set("Nats-Schedule-Target", "foo.publish")
pubAck, err := js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
sl := c.streamLeader(globalAccountName, "SchedulesEnabled")
mset, err := sl.globalAccount().lookupStream("SchedulesEnabled")
require_NoError(t, err)
// Waiting for the repeated message to be published enough times.
checkFor(t, 4*time.Second, 200*time.Millisecond, func() error {
total := mset.Store().SubjectsTotals("foo.publish")["foo.publish"]
if total < 3 {
return fmt.Errorf("expected at least 3 messages, got %d", total)
}
return nil
})
// Servers should be synced.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "SchedulesEnabled")
})
// If we remove the schedule, the scheduling should stop.
var removed bool
total := mset.Store().SubjectsTotals("foo.publish")["foo.publish"]
for i := 1; ; i++ {
before := total
time.Sleep(1200 * time.Millisecond)
total = mset.Store().SubjectsTotals("foo.publish")["foo.publish"]
if before == total {
// If the scheduling stopped before we removed, that's a problem.
require_True(t, removed)
break
}
if !removed {
removed = true
require_NoError(t, js.DeleteMsg("SchedulesEnabled", pubAck.Sequence))
}
if i > 2 {
t.Fatal("Scheduling didn't stop")
}
}
// Servers should be synced.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "SchedulesEnabled")
})
})
}
}
}
func TestJetStreamClusterOfflineStreamAndConsumerAfterAssetCreateOrUpdate(t *testing.T) {
clusterName := "R3S"
c := createJetStreamClusterExplicit(t, clusterName, 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
ml := c.leader()
require_NotNil(t, ml)
sjs := ml.getJetStream()
require_NotNil(t, sjs)
sjs.mu.Lock()
cc := sjs.cluster
if cc == nil || cc.meta == nil {
sjs.mu.Unlock()
t.Fatalf("Expected cluster to be initialized")
}
restart := func() {
t.Helper()
for _, s := range c.servers {
sjs = s.getJetStream()
snap, _, _, err := sjs.metaSnapshot()
require_NoError(t, err)
meta := sjs.getMetaGroup()
meta.InstallSnapshot(snap, false)
}
c.stopAll()
c.restartAllSamePorts()
c.waitOnLeader()
ml = c.leader()
require_NotNil(t, ml)
require_NoError(t, nc.ForceReconnect())
sjs = ml.getJetStream()
require_NotNil(t, sjs)
sjs.mu.Lock()
cc = sjs.cluster
if cc == nil || cc.meta == nil {
sjs.mu.Unlock()
t.Fatalf("Expected cluster to be initialized")
}
sjs.mu.Unlock()
}
getValidMetaSnapshot := func() (wsas []writeableStreamAssignment) {
t.Helper()
snap, _, _, err := sjs.metaSnapshot()
require_NoError(t, err)
require_True(t, len(snap) > 0)
dec, err := s2.Decode(nil, snap)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(dec, &wsas))
return wsas
}
// Create a stream that's unsupported.
ci := &ClientInfo{
Account: globalAccountName,
Cluster: clusterName,
}
scfg := &StreamConfig{
Name: "DowngradeStreamTest",
Storage: FileStorage,
Replicas: 3,
Metadata: map[string]string{"_nats.req.level": strconv.Itoa(math.MaxInt - 1)},
}
rg, perr := sjs.createGroupForStream(ci, scfg)
if perr != nil {
sjs.mu.Unlock()
require_NoError(t, perr)
}
sa := &streamAssignment{
Config: scfg,
Sync: syncSubjForStream(),
Group: rg,
Created: time.Now().UTC(),
Client: ci,
}
err := cc.meta.Propose(encodeAddStreamAssignment(sa))
sjs.mu.Unlock()
require_NoError(t, err)
c.waitOnAllCurrent()
unsupported := func(requiredApiLevel int) string {
return fmt.Sprintf("unsupported - required API level: %d, current API level: %d", requiredApiLevel, JSApiLevel)
}
expectStreamInfo := func(offlineReason, streamName string) {
var msg *nats.Msg
checkFor(t, 3*time.Second, 200*time.Millisecond, func() error {
msg, err = nc.Request(fmt.Sprintf(JSApiStreamInfoT, streamName), nil, time.Second)
return err
})
var si JSApiStreamInfoResponse
require_NoError(t, json.Unmarshal(msg.Data, &si))
require_NotNil(t, si.Error)
require_Error(t, si.Error, NewJSStreamOfflineReasonError(errors.New(offlineReason)))
var sn JSApiStreamNamesResponse
msg, err = nc.Request(JSApiStreams, nil, time.Second)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(msg.Data, &sn))
require_Len(t, len(sn.Streams), 1)
require_Equal(t, sn.Streams[0], streamName)
var sl JSApiStreamListResponse
msg, err = nc.Request(JSApiStreamList, nil, 2*time.Second)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(msg.Data, &sl))
require_Len(t, len(sl.Streams), 0)
require_Len(t, len(sl.Missing), 1)
require_Equal(t, sl.Missing[0], streamName)
require_Len(t, len(sl.Offline), 1)
require_Equal(t, sl.Offline[streamName], offlineReason)
}
// Stream should be reported as offline, but healthz should report healthy to not block downgrades.
expectStreamInfo(unsupported(math.MaxInt-1), "DowngradeStreamTest")
health := ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
restart()
expectStreamInfo(unsupported(math.MaxInt-1), "DowngradeStreamTest")
health = ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
wsas := getValidMetaSnapshot()
require_Len(t, len(wsas), 1)
nsa := &streamAssignment{ConfigJSON: wsas[0].ConfigJSON}
require_NoError(t, decodeStreamAssignmentConfig(ml, nsa))
require_Equal(t, nsa.Config.Name, "DowngradeStreamTest")
require_Equal(t, nsa.Config.Metadata["_nats.req.level"], strconv.Itoa(math.MaxInt-1))
// Update a stream that's unsupported.
sjs.mu.Lock()
scfg.Metadata = map[string]string{"_nats.req.level": strconv.Itoa(math.MaxInt)}
err = cc.meta.Propose(encodeUpdateStreamAssignment(sa))
sjs.mu.Unlock()
require_NoError(t, err)
c.waitOnAllCurrent()
// Stream should be reported as offline, but healthz should report healthy to not block downgrades.
expectStreamInfo(unsupported(math.MaxInt), "DowngradeStreamTest")
health = ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
restart()
expectStreamInfo(unsupported(math.MaxInt), "DowngradeStreamTest")
health = ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
wsas = getValidMetaSnapshot()
require_Len(t, len(wsas), 1)
nsa = &streamAssignment{ConfigJSON: wsas[0].ConfigJSON}
require_NoError(t, decodeStreamAssignmentConfig(ml, nsa))
require_Equal(t, nsa.Config.Name, "DowngradeStreamTest")
require_Equal(t, nsa.Config.Metadata["_nats.req.level"], strconv.Itoa(math.MaxInt))
// Deleting a stream should always work, even if it is unsupported.
require_NoError(t, js.DeleteStream("DowngradeStreamTest"))
snap, _, _, err := sjs.metaSnapshot()
require_NoError(t, err)
require_True(t, snap == nil)
// Create a supported stream and consumer.
_, err = js.AddStream(&nats.StreamConfig{Name: "DowngradeConsumerTest", Replicas: 3})
require_NoError(t, err)
_, err = js.AddConsumer("DowngradeConsumerTest", &nats.ConsumerConfig{Name: "consumer"})
require_NoError(t, err)
// Create a consumer that's unsupported.
sjs.mu.Lock()
ccfg := &ConsumerConfig{
Name: "DowngradeConsumerTest",
Replicas: 3,
Metadata: map[string]string{"_nats.req.level": strconv.Itoa(math.MaxInt - 1)},
}
rg = cc.createGroupForConsumer(ccfg, sa)
ca := &consumerAssignment{
Config: ccfg,
Group: rg,
Stream: "DowngradeConsumerTest",
Name: "DowngradeConsumerTest",
Created: time.Now().UTC(),
Client: ci,
}
err = cc.meta.Propose(encodeAddConsumerAssignment(ca))
sjs.mu.Unlock()
require_NoError(t, err)
c.waitOnAllCurrent()
expectConsumerInfo := func(offlineReason string) {
var msg *nats.Msg
checkFor(t, 3*time.Second, 200*time.Millisecond, func() error {
msg, err = nc.Request(fmt.Sprintf(JSApiConsumerInfoT, "DowngradeConsumerTest", "DowngradeConsumerTest"), nil, time.Second)
return err
})
var ci JSApiConsumerInfoResponse
require_NoError(t, json.Unmarshal(msg.Data, &ci))
require_NotNil(t, ci.Error)
require_Error(t, ci.Error, NewJSConsumerOfflineReasonError(errors.New(offlineReason)))
var cn JSApiConsumerNamesResponse
msg, err = nc.Request(fmt.Sprintf(JSApiConsumersT, "DowngradeConsumerTest"), nil, time.Second)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(msg.Data, &cn))
require_Len(t, len(cn.Consumers), 2)
require_Equal(t, cn.Consumers[0], "DowngradeConsumerTest")
for _, name := range cn.Consumers {
require_True(t, name == "consumer" || name == "DowngradeConsumerTest")
}
var cl JSApiConsumerListResponse
msg, err = nc.Request(fmt.Sprintf(JSApiConsumerListT, "DowngradeConsumerTest"), nil, 5*time.Second)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(msg.Data, &cl))
require_Len(t, len(cl.Consumers), 0)
require_Len(t, len(cl.Missing), 2)
for _, name := range cl.Missing {
require_True(t, name == "consumer" || name == "DowngradeConsumerTest")
}
require_Len(t, len(cl.Offline), 1)
require_Equal(t, cl.Offline["DowngradeConsumerTest"], offlineReason)
// Stream should also be reported as offline.
// Specifically, as "stopped" because it's still supported, but can't run due to the unsupported consumer.
expectStreamInfo("stopped - unsupported consumer \"DowngradeConsumerTest\"", "DowngradeConsumerTest")
}
// Consumer should be reported as offline, but healthz should report healthy to not block downgrades.
expectConsumerInfo(unsupported(math.MaxInt - 1))
health = ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
restart()
expectConsumerInfo(unsupported(math.MaxInt - 1))
health = ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
wsas = getValidMetaSnapshot()
require_Len(t, len(wsas), 1)
nsa = &streamAssignment{ConfigJSON: wsas[0].ConfigJSON}
require_NoError(t, decodeStreamAssignmentConfig(ml, nsa))
require_Equal(t, nsa.Config.Name, "DowngradeConsumerTest")
require_Equal(t, nsa.Config.Metadata["_nats.req.level"], "0")
require_Len(t, len(wsas[0].Consumers), 2)
for _, wca := range wsas[0].Consumers {
nca := &consumerAssignment{ConfigJSON: wca.ConfigJSON}
require_NoError(t, decodeConsumerAssignmentConfig(nca))
if nca.Config.Name == "DowngradeConsumerTest" {
require_Equal(t, nca.Config.Metadata["_nats.req.level"], strconv.Itoa(math.MaxInt-1))
} else {
require_Equal(t, nca.Config.Name, "consumer")
}
}
// Update a consumer (with compressed data) that's unsupported.
ccfg.Metadata = map[string]string{"_nats.req.level": strconv.Itoa(math.MaxInt)}
sjs.mu.Lock()
err = cc.meta.Propose(encodeAddConsumerAssignmentCompressed(ca))
sjs.mu.Unlock()
require_NoError(t, err)
c.waitOnAllCurrent()
// Consumer should be reported as offline, but healthz should report healthy to not block downgrades.
expectConsumerInfo(unsupported(math.MaxInt))
health = ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
restart()
expectConsumerInfo(unsupported(math.MaxInt))
health = ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
wsas = getValidMetaSnapshot()
require_Len(t, len(wsas), 1)
nsa = &streamAssignment{ConfigJSON: wsas[0].ConfigJSON}
require_NoError(t, decodeStreamAssignmentConfig(ml, nsa))
require_Equal(t, nsa.Config.Name, "DowngradeConsumerTest")
require_Equal(t, nsa.Config.Metadata["_nats.req.level"], "0")
require_Len(t, len(wsas[0].Consumers), 2)
for _, wca := range wsas[0].Consumers {
nca := &consumerAssignment{ConfigJSON: wca.ConfigJSON}
require_NoError(t, decodeConsumerAssignmentConfig(nca))
if nca.Config.Name == "DowngradeConsumerTest" {
require_Equal(t, nca.Config.Metadata["_nats.req.level"], strconv.Itoa(math.MaxInt))
} else {
require_Equal(t, nca.Config.Name, "consumer")
}
}
// Deleting a consumer should always work, even if it is unsupported.
require_NoError(t, js.DeleteConsumer("DowngradeConsumerTest", "DowngradeConsumerTest"))
c.waitOnAllCurrent()
wsas = getValidMetaSnapshot()
require_Len(t, len(wsas), 1)
nsa = &streamAssignment{ConfigJSON: wsas[0].ConfigJSON}
require_NoError(t, decodeStreamAssignmentConfig(ml, nsa))
require_Equal(t, nsa.Config.Name, "DowngradeConsumerTest")
require_Equal(t, nsa.Config.Metadata["_nats.req.level"], "0")
require_Len(t, len(wsas[0].Consumers), 1)
nca := &consumerAssignment{ConfigJSON: wsas[0].Consumers[0].ConfigJSON}
require_NoError(t, decodeConsumerAssignmentConfig(nca))
require_Equal(t, nca.Config.Name, "consumer")
}
func TestJetStreamClusterOfflineStreamAndConsumerAfterDowngrade(t *testing.T) {
clusterName := "R3S"
c := createJetStreamClusterExplicit(t, clusterName, 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
ml := c.leader()
require_NotNil(t, ml)
sjs := ml.getJetStream()
require_NotNil(t, sjs)
sjs.mu.Lock()
cc := sjs.cluster
if cc == nil || cc.meta == nil {
sjs.mu.Unlock()
t.Fatalf("Expected cluster to be initialized")
}
restart := func() {
t.Helper()
for _, s := range c.servers {
sjs = s.getJetStream()
snap, _, _, err := sjs.metaSnapshot()
require_NoError(t, err)
meta := sjs.getMetaGroup()
meta.InstallSnapshot(snap, false)
}
c.stopAll()
c.restartAllSamePorts()
c.waitOnLeader()
ml = c.leader()
require_NotNil(t, ml)
require_NoError(t, nc.ForceReconnect())
sjs = ml.getJetStream()
require_NotNil(t, sjs)
sjs.mu.Lock()
cc = sjs.cluster
if cc == nil || cc.meta == nil {
sjs.mu.Unlock()
t.Fatalf("Expected cluster to be initialized")
}
sjs.mu.Unlock()
}
getValidMetaSnapshot := func() (wsas []writeableStreamAssignment) {
t.Helper()
snap, _, _, err := sjs.metaSnapshot()
require_NoError(t, err)
require_True(t, len(snap) > 0)
dec, err := s2.Decode(nil, snap)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(dec, &wsas))
return wsas
}
// Create a stream that's unsupported.
ci := &ClientInfo{
Account: globalAccountName,
Cluster: clusterName,
}
scfg := &StreamConfig{
Name: "DowngradeStreamTest",
Storage: FileStorage,
Replicas: 3,
}
rg, perr := sjs.createGroupForStream(ci, scfg)
if perr != nil {
sjs.mu.Unlock()
require_NoError(t, perr)
}
sa := &streamAssignment{
Config: scfg,
Sync: syncSubjForStream(),
Group: rg,
Created: time.Now().UTC(),
Client: ci,
}
err := cc.meta.Propose(encodeAddStreamAssignment(sa))
sjs.mu.Unlock()
require_NoError(t, err)
c.waitOnStreamLeader(globalAccountName, "DowngradeStreamTest")
expectStreamInfo := func(offline bool) {
if !offline {
c.waitOnStreamLeader(globalAccountName, "DowngradeStreamTest")
}
var msg *nats.Msg
checkFor(t, 3*time.Second, 200*time.Millisecond, func() error {
msg, err = nc.Request(fmt.Sprintf(JSApiStreamInfoT, "DowngradeStreamTest"), nil, time.Second)
return err
})
var si JSApiStreamInfoResponse
require_NoError(t, json.Unmarshal(msg.Data, &si))
if !offline {
require_True(t, si.Error == nil)
} else {
require_NotNil(t, si.Error)
require_Contains(t, si.Error.Error(), "stream is offline", "unsupported", "required API level")
}
}
// Stream is still supported, so it should be available and healthz should report healthy.
expectStreamInfo(false)
health := ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
restart()
expectStreamInfo(false)
health = ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
wsas := getValidMetaSnapshot()
require_Len(t, len(wsas), 1)
nsa := &streamAssignment{ConfigJSON: wsas[0].ConfigJSON}
require_NoError(t, decodeStreamAssignmentConfig(ml, nsa))
require_Equal(t, nsa.Config.Name, "DowngradeStreamTest")
// Update a stream to be unsupported.
sjs.mu.Lock()
scfg.Metadata = map[string]string{"_nats.req.level": strconv.Itoa(math.MaxInt)}
err = cc.meta.Propose(encodeUpdateStreamAssignment(sa))
sjs.mu.Unlock()
require_NoError(t, err)
c.waitOnAllCurrent()
// Stream should be reported as offline, but healthz should report healthy to not block downgrades.
expectStreamInfo(true)
health = ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
restart()
expectStreamInfo(true)
health = ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
wsas = getValidMetaSnapshot()
require_Len(t, len(wsas), 1)
nsa = &streamAssignment{ConfigJSON: wsas[0].ConfigJSON}
require_NoError(t, decodeStreamAssignmentConfig(ml, nsa))
require_Equal(t, nsa.Config.Name, "DowngradeStreamTest")
// Deleting a stream should always work, even if it is unsupported.
require_NoError(t, js.DeleteStream("DowngradeStreamTest"))
snap, _, _, err := sjs.metaSnapshot()
require_NoError(t, err)
require_True(t, snap == nil)
// Create a supported stream and consumer.
_, err = js.AddStream(&nats.StreamConfig{Name: "DowngradeConsumerTest", Replicas: 3})
require_NoError(t, err)
sjs.mu.Lock()
ccfg := &ConsumerConfig{
Name: "DowngradeConsumerTest",
Replicas: 3,
MaxWaiting: JSWaitQueueDefaultMax,
}
rg = cc.createGroupForConsumer(ccfg, sa)
ca := &consumerAssignment{
Config: ccfg,
Group: rg,
Stream: "DowngradeConsumerTest",
Name: "DowngradeConsumerTest",
Created: time.Now().UTC(),
Client: ci,
}
err = cc.meta.Propose(encodeAddConsumerAssignment(ca))
sjs.mu.Unlock()
require_NoError(t, err)
c.waitOnConsumerLeader(globalAccountName, "DowngradeConsumerTest", "DowngradeConsumerTest")
expectConsumerInfo := func(offline bool) {
if !offline {
c.waitOnConsumerLeader(globalAccountName, "DowngradeConsumerTest", "DowngradeConsumerTest")
}
var msg *nats.Msg
checkFor(t, 3*time.Second, 200*time.Millisecond, func() error {
msg, err = nc.Request(fmt.Sprintf(JSApiConsumerInfoT, "DowngradeConsumerTest", "DowngradeConsumerTest"), nil, 2*time.Second)
return err
})
var ci JSApiConsumerInfoResponse
require_NoError(t, json.Unmarshal(msg.Data, &ci))
if !offline {
require_True(t, ci.Error == nil)
} else {
require_NotNil(t, ci.Error)
require_Contains(t, ci.Error.Error(), "consumer is offline", "unsupported", "required API level")
}
}
// Consumer is still supported, so it should be available and healthz should report healthy.
expectConsumerInfo(false)
health = ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
restart()
expectConsumerInfo(false)
health = ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
wsas = getValidMetaSnapshot()
require_Len(t, len(wsas), 1)
nsa = &streamAssignment{ConfigJSON: wsas[0].ConfigJSON}
require_NoError(t, decodeStreamAssignmentConfig(ml, nsa))
require_Equal(t, nsa.Config.Name, "DowngradeConsumerTest")
require_Len(t, len(wsas[0].Consumers), 1)
// Update a consumer to be unsupported.
ccfg.Metadata = map[string]string{"_nats.req.level": strconv.Itoa(math.MaxInt)}
sjs.mu.Lock()
err = cc.meta.Propose(encodeAddConsumerAssignment(ca))
sjs.mu.Unlock()
require_NoError(t, err)
c.waitOnAllCurrent()
// Consumer should be reported as offline, but healthz should report healthy to not block downgrades.
expectConsumerInfo(true)
health = ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
restart()
expectConsumerInfo(true)
health = ml.healthz(&HealthzOptions{})
require_Equal(t, health.StatusCode, 200)
wsas = getValidMetaSnapshot()
require_Len(t, len(wsas), 1)
nsa = &streamAssignment{ConfigJSON: wsas[0].ConfigJSON}
require_NoError(t, decodeStreamAssignmentConfig(ml, nsa))
require_Equal(t, nsa.Config.Name, "DowngradeConsumerTest")
require_Equal(t, nsa.Config.Metadata["_nats.req.level"], "0")
require_Len(t, len(wsas[0].Consumers), 1)
nca := &consumerAssignment{ConfigJSON: wsas[0].Consumers[0].ConfigJSON}
require_NoError(t, decodeConsumerAssignmentConfig(nca))
require_Equal(t, nca.Config.Metadata["_nats.req.level"], strconv.Itoa(math.MaxInt))
}
func TestJetStreamClusterOfflineStreamAndConsumerUpdate(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "DowngradeTest", Replicas: 3})
require_NoError(t, err)
_, err = js.AddConsumer("DowngradeTest", &nats.ConsumerConfig{Durable: "D", Replicas: 3})
require_NoError(t, err)
c.waitOnAllCurrent()
ml := c.leader()
require_NotNil(t, ml)
sjs := ml.getJetStream()
require_NotNil(t, sjs)
var sa *streamAssignment
var ca *consumerAssignment
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
sjs.mu.Lock()
defer sjs.mu.Unlock()
sa = sjs.streamAssignment(globalAccountName, "DowngradeTest")
if sa == nil {
return errors.New("stream assignment missing")
}
ca = sjs.consumerAssignment(globalAccountName, "DowngradeTest", "D")
if ca == nil {
return errors.New("consumer assignment missing")
}
return nil
})
getValidMetaSnapshot := func() (wsas []writeableStreamAssignment) {
t.Helper()
snap, _, _, err := sjs.metaSnapshot()
require_NoError(t, err)
require_True(t, len(snap) > 0)
dec, err := s2.Decode(nil, snap)
require_NoError(t, err)
require_NoError(t, json.Unmarshal(dec, &wsas))
return wsas
}
wsas := getValidMetaSnapshot()
require_Len(t, len(wsas), 1)
nsa := &streamAssignment{ConfigJSON: wsas[0].ConfigJSON}
require_NoError(t, decodeStreamAssignmentConfig(ml, nsa))
require_Equal(t, nsa.Config.Name, "DowngradeTest")
require_Equal(t, nsa.Config.Metadata["_nats.req.level"], "0")
require_Len(t, len(wsas[0].Consumers), 1)
nca := &consumerAssignment{ConfigJSON: wsas[0].Consumers[0].ConfigJSON}
require_NoError(t, decodeConsumerAssignmentConfig(nca))
require_Equal(t, wsas[0].Consumers[0].Name, "D")
require_Equal(t, nca.Config.Metadata["_nats.req.level"], "0")
// Update a consumer to be unsupported.
sjs.mu.Lock()
ca.Config.Metadata = map[string]string{"_nats.req.level": strconv.Itoa(math.MaxInt)}
err = sjs.cluster.meta.Propose(encodeAddConsumerAssignment(ca))
sjs.mu.Unlock()
require_NoError(t, err)
// Update the stream to be unsupported.
sjs.mu.Lock()
sa.Config.Metadata = map[string]string{"_nats.req.level": strconv.Itoa(math.MaxInt)}
err = sjs.cluster.meta.Propose(encodeUpdateStreamAssignment(sa))
sjs.mu.Unlock()
require_NoError(t, err)
c.waitOnAllCurrent()
wsas = getValidMetaSnapshot()
require_Len(t, len(wsas), 1)
nsa = &streamAssignment{ConfigJSON: wsas[0].ConfigJSON}
require_NoError(t, decodeStreamAssignmentConfig(ml, nsa))
require_Equal(t, nsa.Config.Name, "DowngradeTest")
require_Equal(t, nsa.Config.Metadata["_nats.req.level"], strconv.Itoa(math.MaxInt))
require_Len(t, len(wsas[0].Consumers), 1)
nca = &consumerAssignment{ConfigJSON: wsas[0].Consumers[0].ConfigJSON}
require_NoError(t, decodeConsumerAssignmentConfig(nca))
require_Equal(t, wsas[0].Consumers[0].Name, "D")
require_Equal(t, nca.Config.Metadata["_nats.req.level"], strconv.Itoa(math.MaxInt))
}
func TestJetStreamClusterOfflineStreamAndConsumerStrictDecoding(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
unsupportedJson := []byte("{\"unknown\": true}")
unsupportedStreamJson := []byte(fmt.Sprintf("{\"stream\":%s}", unsupportedJson))
unsupportedConsumerJson := []byte(fmt.Sprintf("{\"consumer\":%s}", unsupportedJson))
sa, err := decodeStreamAssignment(s, unsupportedStreamJson)
require_NoError(t, err)
require_True(t, bytes.Equal(sa.ConfigJSON, unsupportedJson))
require_True(t, sa.unsupported != nil)
ca, err := decodeConsumerAssignment(unsupportedConsumerJson)
require_NoError(t, err)
require_True(t, bytes.Equal(ca.ConfigJSON, unsupportedJson))
require_True(t, ca.unsupported != nil)
var bb bytes.Buffer
s2e := s2.NewWriter(&bb)
_, err = s2e.Write(unsupportedConsumerJson)
require_NoError(t, err)
require_NoError(t, s2e.Close())
ca, err = decodeConsumerAssignmentCompressed(bb.Bytes())
require_NoError(t, err)
require_True(t, bytes.Equal(ca.ConfigJSON, unsupportedJson))
require_True(t, ca.unsupported != nil)
}
func TestJetStreamClusterStreamMonitorShutdownWithoutRaftNode(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
for _, s := range c.servers {
if !s.JetStreamIsStreamAssigned(globalAccountName, "TEST") {
return fmt.Errorf("stream not assigned on %s", s.Name())
}
}
return nil
})
var nodes []RaftNode
for _, s := range c.servers {
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
// Manually nil-out the node. This shouldn't happen normally,
// but tests we can shut down purely with the monitor goroutine quit channel.
mset.mu.Lock()
n := mset.node
mset.node = nil
mset.mu.Unlock()
require_NotNil(t, n)
nodes = append(nodes, n)
}
for _, n := range nodes {
require_NotEqual(t, n.State(), Closed)
}
require_NoError(t, js.DeleteStream("TEST"))
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
for _, n := range nodes {
if state := n.State(); state != Closed {
return fmt.Errorf("node not closed on %s: %s", n.ID(), state.String())
}
}
return nil
})
}
func TestJetStreamClusterConsumerMonitorShutdownWithoutRaftNode(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "DURABLE",
Replicas: 3,
})
require_NoError(t, err)
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
for _, s := range c.servers {
_, cc := s.getJetStreamCluster()
if !cc.isConsumerAssigned(s.globalAccount(), "TEST", "DURABLE") {
return fmt.Errorf("consumer not assigned on %s", s.Name())
}
}
return nil
})
var nodes []RaftNode
for _, s := range c.servers {
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("DURABLE")
require_NotNil(t, o)
// Manually nil-out the node. This shouldn't happen normally,
// but tests we can shut down purely with the monitor goroutine quit channel.
o.mu.Lock()
n := o.node
o.node = nil
o.mu.Unlock()
require_NotNil(t, n)
nodes = append(nodes, n)
}
for _, n := range nodes {
require_NotEqual(t, n.State(), Closed)
}
require_NoError(t, js.DeleteStream("TEST"))
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
for _, n := range nodes {
if state := n.State(); state != Closed {
return fmt.Errorf("node not closed on %s: %s", n.ID(), state.String())
}
}
return nil
})
}
func TestJetStreamClusterUnsetEmptyPlacement(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Placement: &nats.Placement{},
}
si, err := js.AddStream(cfg)
require_NoError(t, err)
require_True(t, si.Config.Placement == nil)
si, err = js.UpdateStream(cfg)
require_NoError(t, err)
require_True(t, si.Config.Placement == nil)
// Set a placement level
cfg.Placement = &nats.Placement{Cluster: "R3S"}
si, err = js.UpdateStream(cfg)
require_NoError(t, err)
require_True(t, si.Config.Placement != nil)
require_Equal(t, si.Config.Placement.Cluster, "R3S")
// And ensure it can be reset.
cfg.Placement = &nats.Placement{}
si, err = js.UpdateStream(cfg)
require_NoError(t, err)
require_True(t, si.Config.Placement == nil)
}
func TestJetStreamClusterPersistModeAsync(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc := clientConnectToServer(t, c.randomServer())
defer nc.Close()
cfg := &StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: FileStorage,
Replicas: 3,
PersistMode: AsyncPersistMode,
}
_, err := jsStreamCreate(t, nc, cfg)
require_Error(t, err, NewJSStreamInvalidConfigError(fmt.Errorf("async persist mode is not supported on replicated streams")))
}
func TestJetStreamClusterDeleteMsgEOF(t *testing.T) {
for _, replicas := range []int{1, 3} {
t.Run(fmt.Sprintf("R%d", replicas), func(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: replicas,
})
require_NoError(t, err)
_, err = js.Publish("foo", nil)
require_NoError(t, err)
require_Error(t, js.DeleteMsg("TEST", 0), NewJSNoMessageFoundError())
require_NoError(t, js.DeleteMsg("TEST", 1))
require_Error(t, js.DeleteMsg("TEST", 1), NewJSNoMessageFoundError())
require_Error(t, js.DeleteMsg("TEST", 2), NewJSStreamMsgDeleteFailedError(ErrStoreEOF))
})
}
}
func TestJetStreamClusterCatchupSkipMsgDesync(t *testing.T) {
for _, storage := range []nats.StorageType{nats.FileStorage, nats.MemoryStorage} {
t.Run(storage.String(), func(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: storage,
Replicas: 3,
})
require_NoError(t, err)
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "TEST")
})
rs := c.randomNonStreamLeader(globalAccountName, "TEST")
mset, err := rs.globalAccount().lookupStream("TEST")
require_NoError(t, err)
sa := mset.streamAssignment()
// Make sure this server can't become the leader.
n := mset.raftNode().(*raft)
n.SetObserver(true)
sysNc, err := nats.Connect(rs.ClientURL(), nats.UserInfo("admin", "s3cr3t!"))
require_NoError(t, err)
defer sysNc.Close()
sjs := rs.getJetStream()
sjs.mu.RLock()
syncSubj := sa.Sync
sjs.mu.RUnlock()
// Respond to the catchup with an out-of-order SkipMsg.
var eof bool
sub, err := sysNc.Subscribe(syncSubj, func(msg *nats.Msg) {
if !eof {
msg.Respond(encodeStreamMsg(_EMPTY_, _EMPTY_, nil, nil, 10, 0, false))
eof = true
}
msg.Respond(nil)
})
require_NoError(t, err)
defer sub.Drain()
require_NoError(t, sysNc.Flush()) // Must flush, otherwise our subscription could be too late.
err = mset.processSnapshot(&StreamReplicatedState{FirstSeq: 1, LastSeq: 1}, 1)
require_Error(t, err, errCatchupTooManyRetries)
c.waitOnStreamLeader(globalAccountName, "TEST")
pubAck, err := js.Publish("foo", nil)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "TEST")
})
})
}
}
func TestJetStreamClusterJszRaftLeaderReporting(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "DURABLE",
Replicas: 3,
})
require_NoError(t, err)
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
for _, s := range c.servers {
_, _, jsa := s.globalAccount().getJetStreamFromAccount()
if !jsa.streamAssigned("TEST") {
return fmt.Errorf("stream not assigned on %s", s.Name())
}
if !jsa.consumerAssigned("TEST", "DURABLE") {
return fmt.Errorf("consumer not assigned on %s", s.Name())
}
}
return nil
})
sl := c.streamLeader(globalAccountName, "TEST")
cl := c.consumerLeader(globalAccountName, "TEST", "DURABLE")
for _, s := range c.servers {
jsi, err := s.Jsz(&JSzOptions{RaftGroups: true})
require_NoError(t, err)
if s == sl {
require_Equal(t, jsi.StreamsLeader, 1)
} else {
require_Equal(t, jsi.StreamsLeader, 0)
}
if s == cl {
require_Equal(t, jsi.ConsumersLeader, 1)
} else {
require_Equal(t, jsi.ConsumersLeader, 0)
}
}
}
func TestJetStreamClusterNoInterestDesyncOnConsumerCreate(t *testing.T) {
test := func(t *testing.T, twoConsumers bool) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
Replicas: 3,
Retention: nats.InterestPolicy,
})
require_NoError(t, err)
// Pick a random server that will not know about the new consumer being created.
// If servers determine "no interest" individually, these servers will desync.
rs := c.randomNonLeader()
sjs := rs.getJetStream()
meta := sjs.getMetaGroup()
require_NoError(t, meta.PauseApply())
sub, err := js.PullSubscribe("foo", "DURABLE", nats.BindStream("TEST"))
require_NoError(t, err)
defer sub.Drain()
checkConsumersAssigned := func(expected int) {
t.Helper()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
var count int
for _, s := range c.servers {
mset, err := s.globalAccount().lookupStream("TEST")
if err != nil {
return err
}
count += mset.numConsumers()
}
if count != expected {
return fmt.Errorf("expected %d, got %d", expected, count)
}
return nil
})
}
// Confirm only two servers know about the consumer.
checkConsumersAssigned(2)
c.waitOnConsumerLeader(globalAccountName, "TEST", "DURABLE")
// Publish a single message. All servers will receive this, but only two will store it.
_, err = js.Publish("foo", nil)
require_NoError(t, err)
checkLastSeq := func(lseq uint64) {
t.Helper()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
for _, s := range c.servers {
mset, err := s.globalAccount().lookupStream("TEST")
if err != nil {
return err
}
if seq := mset.lastSeq(); seq != lseq {
return fmt.Errorf("expected %d, got %d", lseq, seq)
}
}
return nil
})
}
checkLastSeq(1)
// Resume the meta layer such that the consumer gets created on the remaining server.
meta.ResumeApply()
checkConsumersAssigned(3)
if twoConsumers {
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "DURABLE2", FilterSubject: "bar"})
require_NoError(t, err)
checkConsumersAssigned(6)
}
// All servers will now store another published message.
_, err = js.Publish("foo", nil)
require_NoError(t, err)
checkLastSeq(2)
// Make sure the consumer leader is on the same server that didn't store the first message.
cl := c.consumerLeader(globalAccountName, "TEST", "DURABLE")
if cl != rs {
mset, err := cl.globalAccount().lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("DURABLE")
require_NotNil(t, o)
n := o.raftNode()
require_NoError(t, n.StepDown(rs.NodeName()))
c.waitOnConsumerLeader(globalAccountName, "TEST", "DURABLE")
cl = c.consumerLeader(globalAccountName, "TEST", "DURABLE")
require_Equal(t, cl, rs)
}
// Since the consumer leader is the same as the server that didn't store the first message,
// it can only receive and ack the second message.
msgs, err := sub.Fetch(1, nats.MaxWait(time.Second))
require_NoError(t, err)
require_Len(t, len(msgs), 1)
metadata, err := msgs[0].Metadata()
require_NoError(t, err)
require_Equal(t, metadata.Sequence.Stream, 2)
require_Equal(t, metadata.NumPending, 0)
require_NoError(t, msgs[0].AckSync())
if twoConsumers {
require_NoError(t, js.DeleteConsumer("TEST", "DURABLE"))
}
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
// The servers will eventually be synced up again, but this relies on the interest state being checked.
for _, s := range c.servers {
if s == rs {
continue
}
mset, err := s.globalAccount().lookupStream("TEST")
if err != nil {
return err
}
mset.checkInterestState()
}
return checkState(t, c, globalAccountName, "TEST")
})
}
t.Run("OneConsumer", func(t *testing.T) { test(t, false) })
t.Run("TwoConsumers", func(t *testing.T) { test(t, true) })
}
func TestJetStreamClusterRaftCatchupSignalsMetaRecovery(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
rs := c.randomNonLeader()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
if !rs.JetStreamIsStreamAssigned(globalAccountName, "TEST") {
return errors.New("not assigned")
}
return nil
})
sjs := rs.getJetStream()
sjs.mu.Lock()
sa := sjs.streamAssignment(globalAccountName, "TEST")
encodedDelete := encodeDeleteStreamAssignment(sa)
sjs.mu.Unlock()
meta := sjs.getMetaGroup().(*raft)
apply := meta.ApplyQ()
// Should not panic if we receive a nil entry "randomly".
_, err = apply.push(nil)
require_NoError(t, err)
// Should put us in upper-layer recovery mode.
// For this test using two large values so we remain in catchup.
meta.Lock()
meta.createCatchup(&appendEntry{pterm: 100, pindex: 100})
meta.sendCatchupSignal()
meta.Unlock()
// Deleting a stream should be staged, not immediately performed.
_, err = apply.push(newCommittedEntry(1, []*Entry{{EntryNormal, encodedDelete}}))
require_NoError(t, err)
time.Sleep(200 * time.Millisecond)
require_True(t, rs.JetStreamIsStreamAssigned(globalAccountName, "TEST"))
// Canceling the catchup, because it's completed, should result in the staged changes to be applied.
meta.Lock()
meta.cancelCatchup()
meta.Unlock()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
if rs.JetStreamIsStreamAssigned(globalAccountName, "TEST") {
return errors.New("still assigned")
}
return nil
})
}
func TestJetStreamClusterRaftCatchupSignalsMetaRecoveryRecreateStream(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
Storage: nats.FileStorage,
})
require_NoError(t, err)
rs := c.randomNonLeader()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
if !rs.JetStreamIsStreamAssigned(globalAccountName, "TEST") {
return errors.New("not assigned")
}
return nil
})
sjs := rs.getJetStream()
sjs.mu.Lock()
sa := sjs.streamAssignment(globalAccountName, "TEST")
encodedDelete := encodeDeleteStreamAssignment(sa)
sa.Config.Storage = MemoryStorage
encodedAdd := encodeAddStreamAssignment(sa)
encodedUpdate := encodeUpdateStreamAssignment(sa)
sjs.mu.Unlock()
meta := sjs.getMetaGroup().(*raft)
apply := meta.ApplyQ()
// Should not panic if we receive a nil entry "randomly".
_, err = apply.push(nil)
require_NoError(t, err)
// Should put us in upper-layer recovery mode.
// For this test using two large values so we remain in catchup.
meta.Lock()
meta.createCatchup(&appendEntry{pterm: 100, pindex: 100})
meta.Unlock()
// Deleting a stream should be staged, not immediately performed.
_, err = apply.push(newCommittedEntry(1, []*Entry{
{EntryNormal, encodedDelete},
{EntryNormal, encodedAdd},
{EntryNormal, encodedUpdate},
}))
require_NoError(t, err)
// Canceling the catchup, because it's completed, should result in the staged changes to be applied.
meta.Lock()
meta.cancelCatchup()
meta.Unlock()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
mset, err := rs.globalAccount().lookupStream("TEST")
if err != nil {
return err
}
cfg := mset.config()
if cfg.Storage != MemoryStorage {
return errors.New("still the old stream config")
}
return nil
})
}
func TestJetStreamClusterRaftCatchupSignalsMetaRecoveryRecreateConsumer(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
sub, err := js.PullSubscribe(_EMPTY_, "CONSUMER", nats.ConsumerReplicas(3), nats.BindStream("TEST"))
require_NoError(t, err)
defer sub.Drain()
_, err = js.Publish("foo", nil)
require_NoError(t, err)
msgs, err := sub.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_NoError(t, msgs[0].AckSync())
rs := c.randomNonLeader()
sjs := rs.getJetStream()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
mset, err := rs.globalAccount().lookupStream("TEST")
if err != nil {
return err
}
o := mset.lookupConsumer("CONSUMER")
if o == nil {
return errors.New("not found")
}
state, err := o.store.State()
if err != nil {
return err
}
if state.Delivered.Stream != 1 || state.AckFloor.Stream != 1 {
return errors.New("incorrect delivered/ack state")
}
return nil
})
sjs.mu.Lock()
ca := sjs.consumerAssignment(globalAccountName, "TEST", "CONSUMER")
encodedDelete := encodeDeleteConsumerAssignment(ca)
cca := ca.copyGroup()
cca.Group.Peers = []string{rs.NodeName()}
cca.Group.Name = groupNameForConsumer(cca.Group.Peers, FileStorage)
encodedAdd := encodeAddConsumerAssignment(cca)
sjs.mu.Unlock()
meta := sjs.getMetaGroup().(*raft)
apply := meta.ApplyQ()
// Should not panic if we receive a nil entry "randomly".
_, err = apply.push(nil)
require_NoError(t, err)
// Should put us in upper-layer recovery mode.
// For this test using two large values so we remain in catchup.
meta.Lock()
meta.createCatchup(&appendEntry{pterm: 100, pindex: 100})
meta.Unlock()
// Deleting a consumer should be staged, not immediately performed.
_, err = apply.push(newCommittedEntry(1, []*Entry{
{EntryNormal, encodedDelete},
{EntryNormal, encodedAdd},
}))
require_NoError(t, err)
// Canceling the catchup, because it's completed, should result in the staged changes to be applied.
meta.Lock()
meta.cancelCatchup()
meta.Unlock()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
mset, err := rs.globalAccount().lookupStream("TEST")
if err != nil {
return err
}
o := mset.lookupConsumer("CONSUMER")
if o == nil {
return errors.New("not found")
}
state, err := o.store.State()
if err != nil {
return err
}
if state.Delivered.Stream != 0 || state.AckFloor.Stream != 0 {
return errors.New("old delivered/ack state")
}
return nil
})
}
func TestJetStreamClusterMetaRecoveryRecreateStream(t *testing.T) {
test := func(t *testing.T, newStream bool) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
// Create a stream containing 5 messages.
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
}
_, err := js.AddStream(cfg)
require_NoError(t, err)
for range 5 {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
// Select a server that's neither meta nor stream leader.
var rs *Server
for _, s := range c.servers {
if s.JetStreamIsLeader() || s.JetStreamIsStreamLeader(globalAccountName, "TEST") {
continue
}
rs = s
}
// When testing old stream state is cleaned up, pause the meta layer now.
// Otherwise, we'll pause after creating the new stream.
meta := rs.getJetStream().getMetaGroup()
if !newStream {
require_NoError(t, meta.PauseApply())
}
// Delete the stream.
require_NoError(t, js.DeleteStream("TEST"))
if newStream {
// Need to wait for the stream to be deleted on our selected server.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
if rs.JetStreamIsStreamAssigned(globalAccountName, "TEST") {
return errors.New("still assigned")
}
return nil
})
}
// Recreate the stream with a single message.
_, err = js.AddStream(cfg)
require_NoError(t, err)
_, err = js.Publish("foo", nil)
require_NoError(t, err)
if newStream {
// Wait for the state to be the new state, then pause the meta layer.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "TEST")
})
require_NoError(t, meta.PauseApply())
// Also create a snapshot so that the stream can't "cheat" by replaying Raft entries.
mset, err := rs.globalAccount().lookupStream("TEST")
require_NoError(t, err)
streamNode := mset.raftNode()
require_NoError(t, streamNode.InstallSnapshot(mset.stateSnapshot(), false))
}
// Although the meta node is paused, we add one more meta entry to move the commit up one more.
_, err = js.AddStream(&nats.StreamConfig{Name: "RANDOM", Replicas: 3})
require_NoError(t, err)
// We'll now restart this server.
meta.Stop()
meta.WaitForStop()
rs.Shutdown()
if newStream {
// If we're testing new state, make sure we can't recover from our peers.
for _, s := range c.servers {
if s == rs {
continue
}
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
n := mset.raftNode()
n.SetObserver(true)
n.StepDown()
}
}
rs = c.restartServer(rs)
c.waitOnServerCurrent(rs)
// We should arrive at the new state.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
mset, err := rs.globalAccount().lookupStream("TEST")
if err != nil {
return err
}
state := mset.state()
if state.Msgs == 1 {
return nil
}
return fmt.Errorf("incorrect amount of messages: %d", state.Msgs)
})
}
t.Run("OldStream", func(t *testing.T) { test(t, false) })
t.Run("NewStream", func(t *testing.T) { test(t, true) })
}
func TestJetStreamClusterMetaRecoveryRecreateConsumer(t *testing.T) {
test := func(t *testing.T, newConsumer bool) {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
// Create a stream containing 5 messages.
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
for range 5 {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
// Create a consumer and ack all 5 messages.
cfg := &nats.ConsumerConfig{
Durable: "CONSUMER",
Replicas: 3,
AckPolicy: nats.AckExplicitPolicy,
}
_, err = js.AddConsumer("TEST", cfg)
require_NoError(t, err)
sub, err := js.PullSubscribe(_EMPTY_, "CONSUMER", nats.Bind("TEST", "CONSUMER"))
require_NoError(t, err)
defer sub.Drain()
msgs, err := sub.Fetch(5)
require_NoError(t, err)
require_Len(t, len(msgs), 5)
for _, msg := range msgs {
require_NoError(t, msg.AckSync())
}
// Select a server that's neither meta nor consumer leader.
var rs *Server
for _, s := range c.servers {
if s.JetStreamIsLeader() || s.JetStreamIsConsumerLeader(globalAccountName, "TEST", "CONSUMER") {
continue
}
rs = s
}
// When testing old consumer state is cleaned up, pause the meta layer now.
// Otherwise, we'll pause after creating the new consumer.
meta := rs.getJetStream().getMetaGroup()
if !newConsumer {
require_NoError(t, meta.PauseApply())
}
// Delete the consumer.
sub.Drain()
require_NoError(t, js.DeleteConsumer("TEST", "CONSUMER"))
if newConsumer {
// Need to wait for the consumer to be deleted on our selected server.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
if rs.getJetStream().consumerAssignment(globalAccountName, "TEST", "CONSUMER") != nil {
return errors.New("still assigned")
}
return nil
})
}
// Recreate the consumer and ack a single message.
_, err = js.AddConsumer("TEST", cfg)
require_NoError(t, err)
sub, err = js.PullSubscribe(_EMPTY_, "CONSUMER", nats.Bind("TEST", "CONSUMER"))
require_NoError(t, err)
defer sub.Drain()
msgs, err = sub.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_NoError(t, msgs[0].AckSync())
if newConsumer {
// Wait for the state to be the new state, then pause the meta layer.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
for _, s := range c.servers {
mset, err := s.globalAccount().lookupStream("TEST")
if err != nil {
return err
}
o := mset.lookupConsumer("CONSUMER")
if o == nil {
return errors.New("consumer not found")
}
state, err := o.store.State()
if err != nil {
return err
}
if state.Delivered.Stream != 1 || state.AckFloor.Stream != 1 {
return errors.New("incorrect delivered/ack state")
}
}
return nil
})
require_NoError(t, meta.PauseApply())
}
// Although the meta node is paused, we add one more meta entry to move the commit up one more.
_, err = js.AddStream(&nats.StreamConfig{Name: "RANDOM", Replicas: 3})
require_NoError(t, err)
// We'll now restart this server.
sub.Drain()
meta.Stop()
meta.WaitForStop()
rs.Shutdown()
if newConsumer {
// If we're testing new state, make sure we can't recover from our peers.
for _, s := range c.servers {
if s == rs {
continue
}
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
o := mset.lookupConsumer("CONSUMER")
require_NotNil(t, o)
n := o.raftNode()
n.SetObserver(true)
n.StepDown()
}
}
rs = c.restartServer(rs)
c.waitOnServerCurrent(rs)
// We should arrive at the new state.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
mset, err := rs.globalAccount().lookupStream("TEST")
if err != nil {
return err
}
o := mset.lookupConsumer("CONSUMER")
if o == nil {
return errors.New("consumer not found")
}
state, err := o.store.State()
if err != nil {
return err
}
if state.Delivered.Stream != 1 || state.AckFloor.Stream != 1 {
return errors.New("incorrect delivered/ack state")
}
return nil
})
}
t.Run("OldConsumer", func(t *testing.T) { test(t, false) })
t.Run("NewConsumer", func(t *testing.T) { test(t, true) })
}
func TestJetStreamClusterMetaPeerRemoveResponseAfterQuorum(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R5S", 5)
defer c.shutdown()
ml := c.leader()
nc, err := nats.Connect(ml.ClientURL(), nats.UserInfo("admin", "s3cr3t!"))
require_NoError(t, err)
defer nc.Close()
var (
s1 *Server
s2 *Server
s3 *Server
)
for _, s := range c.servers {
if s == ml {
continue
} else if s1 == nil {
s1 = s
} else if s2 == nil {
s2 = s
} else {
s3 = s
break
}
}
// Shutdown a majority.
remove1 := s3.Name()
remove2 := s2.Name()
s1.Shutdown()
s2.Shutdown()
s3.Shutdown()
reply := nats.NewInbox()
sub, err := nc.SubscribeSync(reply)
require_NoError(t, err)
defer sub.Drain()
auditSub, err := nc.SubscribeSync(JSAuditAdvisory)
require_NoError(t, err)
defer auditSub.Drain()
// Since a majority is down, we'll expect to time out since there's no quorum.
req := &JSApiMetaServerRemoveRequest{Server: remove1}
jsreq, err := json.Marshal(req)
require_NoError(t, err)
require_NoError(t, nc.PublishRequest(JSApiRemoveServer, reply, jsreq))
_, err = sub.NextMsg(time.Second)
require_Error(t, err, nats.ErrTimeout)
// Retrying the request should fail, as the leader has already removed it as its peer.
rmsg, err := nc.Request(JSApiRemoveServer, jsreq, time.Second)
require_NoError(t, err)
var resp JSApiMetaServerRemoveResponse
require_NoError(t, json.Unmarshal(rmsg.Data, &resp))
if resp.Error == nil {
t.Fatalf("Expected an error, got none")
}
require_Error(t, resp.Error, NewJSClusterServerMemberChangeInflightError())
// Audit should reflect the same.
rmsg, err = auditSub.NextMsg(time.Second)
require_NoError(t, err)
var auditResp JSAPIAudit
require_NoError(t, json.Unmarshal(rmsg.Data, &auditResp))
resp = JSApiMetaServerRemoveResponse{}
require_NoError(t, json.Unmarshal([]byte(auditResp.Response), &resp))
if resp.Error == nil {
t.Fatalf("Expected an error, got none")
}
require_Error(t, resp.Error, NewJSClusterServerMemberChangeInflightError())
// Don't allow concurrent meta membership changes.
req = &JSApiMetaServerRemoveRequest{Server: remove2}
jsreq, err = json.Marshal(req)
require_NoError(t, err)
rmsg, err = nc.Request(JSApiRemoveServer, jsreq, time.Second)
require_NoError(t, err)
resp = JSApiMetaServerRemoveResponse{}
require_NoError(t, json.Unmarshal(rmsg.Data, &resp))
if resp.Error == nil {
t.Fatalf("Expected an error, got none")
}
require_Error(t, resp.Error, NewJSClusterServerMemberChangeInflightError())
// Audit should reflect the same.
rmsg, err = auditSub.NextMsg(time.Second)
require_NoError(t, err)
auditResp = JSAPIAudit{}
require_NoError(t, json.Unmarshal(rmsg.Data, &auditResp))
resp = JSApiMetaServerRemoveResponse{}
require_NoError(t, json.Unmarshal([]byte(auditResp.Response), &resp))
if resp.Error == nil {
t.Fatalf("Expected an error, got none")
}
require_Error(t, resp.Error, NewJSClusterServerMemberChangeInflightError())
// Bring back one server so that the peer-remove can get quorum now.
// The response should come in shortly after.
c.restartServer(s1)
c.waitOnLeader()
require_Equal(t, c.leader().Name(), ml.Name())
rmsg, err = sub.NextMsg(5 * time.Second)
require_NoError(t, err)
resp = JSApiMetaServerRemoveResponse{}
require_NoError(t, json.Unmarshal(rmsg.Data, &resp))
if resp.Error != nil {
require_NoError(t, resp.Error)
}
require_True(t, resp.Success)
// Audit should reflect the same.
rmsg, err = auditSub.NextMsg(time.Second)
require_NoError(t, err)
auditResp = JSAPIAudit{}
require_NoError(t, json.Unmarshal(rmsg.Data, &auditResp))
resp = JSApiMetaServerRemoveResponse{}
require_NoError(t, json.Unmarshal([]byte(auditResp.Response), &resp))
if resp.Error != nil {
require_NoError(t, resp.Error)
}
require_True(t, resp.Success)
// A retry of the first peer-remove should return an error that the peer is already removed.
// Both a "success" response and this error should be used to know the peer-remove was successful.
req = &JSApiMetaServerRemoveRequest{Server: remove1}
jsreq, err = json.Marshal(req)
require_NoError(t, err)
rmsg, err = nc.Request(JSApiRemoveServer, jsreq, time.Second)
require_NoError(t, err)
resp = JSApiMetaServerRemoveResponse{}
require_NoError(t, json.Unmarshal(rmsg.Data, &resp))
if resp.Error == nil {
t.Fatalf("Expected an error, got none")
}
require_Error(t, resp.Error, NewJSClusterServerNotMemberError())
// Audit should reflect the same.
rmsg, err = auditSub.NextMsg(time.Second)
require_NoError(t, err)
auditResp = JSAPIAudit{}
require_NoError(t, json.Unmarshal(rmsg.Data, &auditResp))
resp = JSApiMetaServerRemoveResponse{}
require_NoError(t, json.Unmarshal([]byte(auditResp.Response), &resp))
if resp.Error == nil {
t.Fatalf("Expected an error, got none")
}
require_Error(t, resp.Error, NewJSClusterServerNotMemberError())
}
func TestJetStreamClusterStreamPeerRemovePeerSetDesync(t *testing.T) {
c := createJetStreamClusterExplicit(t, "R5S", 5)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 3,
})
require_NoError(t, err)
sl := c.streamLeader(globalAccountName, "TEST")
ml := c.leader()
// Map peer IDs to servers.
peerSrv := make(map[string]*Server, len(c.servers))
for _, s := range c.servers {
peerSrv[s.Node()] = s
}
// Get the stream's current peer set from the meta leader.
mjs := ml.getJetStream()
mjs.mu.RLock()
sa := mjs.streamAssignment(globalAccountName, "TEST")
var origPeers []string
if sa != nil {
origPeers = copyStrings(sa.Group.Peers)
}
mjs.mu.RUnlock()
require_Len(t, len(origPeers), 3)
// Pick a stream member to peer-remove that is neither the stream leader nor
// the meta leader. The stream leader must stay in place so it is the one
// processing the updated assignment, and not picking the meta leader means
// stalling this server's meta apply queue doesn't stall the proposal itself.
var rs *Server
for _, p := range origPeers {
if s := peerSrv[p]; s != sl && s != ml {
rs = s
break
}
}
require_NotNil(t, rs)
removedID := rs.Node()
// The stream leader's raft node for the stream group.
mset, err := sl.globalAccount().lookupStream("TEST")
require_NoError(t, err)
srn := mset.raftNode().(*raft)
// Stall the to-be-removed server's meta apply queue. Its stream raft node
// keeps running and acking append entries while being unaware of its own
// removal. This simulates the natural lag between different servers
// applying the same meta entry.
rsMeta := rs.getJetStream().getMetaGroup()
require_NoError(t, rsMeta.PauseApply())
defer rsMeta.ResumeApply()
// Peer-remove the selected stream member.
req := &JSApiStreamRemovePeerRequest{Peer: rs.Name()}
jsreq, err := json.Marshal(req)
require_NoError(t, err)
resp, err := nc.Request(fmt.Sprintf(JSApiStreamRemovePeerT, "TEST"), jsreq, time.Second)
require_NoError(t, err)
var rpResp JSApiStreamRemovePeerResponse
require_NoError(t, json.Unmarshal(resp.Data, &rpResp))
require_True(t, rpResp.Error == nil)
// Wait for the stream leader to apply the updated assignment: a replacement
// peer that was not part of the original peer set must show up in its raft
// peer set.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
srn.RLock()
raftPeers := srn.peerNames()
srn.RUnlock()
for _, p := range raftPeers {
if !slices.Contains(origPeers, p) {
return nil
}
}
return fmt.Errorf("stream leader has not applied the new assignment yet, raft peers: %v", raftPeers)
})
// Observe the reproduction: the removed peer acks the leader's peer state
// append entry (or a heartbeat), the leader treats it as an unknown peer
// and re-adds it via EntryAddPeer. Give it a few heartbeat intervals; on a
// correctly behaving server this never happens.
deadline := time.Now().Add(3 * time.Second)
for time.Now().Before(deadline) {
srn.RLock()
raftPeers := srn.peerNames()
srn.RUnlock()
if len(raftPeers) > 3 {
t.Logf("Removed peer %q was re-added to the raft group, peer set is now: %v", removedID, raftPeers)
break
}
time.Sleep(50 * time.Millisecond)
}
// Let the removed server process its removal and delete its stream raft node.
rsMeta.ResumeApply()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
if _, err = rs.globalAccount().lookupStream("TEST"); err == nil {
return fmt.Errorf("removed server %s still has the stream", rs.Name())
}
return nil
})
// The raft layer peer set on all remaining stream members must converge
// to the meta layer peer set: 3 peers, not including the removed peer.
checkFor(t, 5*time.Second, 200*time.Millisecond, func() error {
// Meta layer peer set, according to the meta leader.
mjs.mu.RLock()
sa = mjs.streamAssignment(globalAccountName, "TEST")
var metaPeers []string
if sa != nil {
metaPeers = copyStrings(sa.Group.Peers)
}
mjs.mu.RUnlock()
if len(metaPeers) != 3 {
return fmt.Errorf("expected 3 peers in meta assignment, got: %v", metaPeers)
}
slices.Sort(metaPeers)
// Each member's raft-layer peer set must match it.
for _, p := range metaPeers {
s := peerSrv[p]
mset, err = s.globalAccount().lookupStream("TEST")
if err != nil {
return fmt.Errorf("stream not found on %s: %v", s.Name(), err)
}
node := mset.raftNode()
if node == nil {
return fmt.Errorf("no raft node for stream on %s", s.Name())
}
rn := node.(*raft)
rn.RLock()
raftPeers := rn.peerNames()
rn.RUnlock()
slices.Sort(raftPeers)
if !slices.Equal(metaPeers, raftPeers) {
return fmt.Errorf("%s: raft peer set %v desynced from meta peer set %v, contains removed peer %q: %v",
s.Name(), raftPeers, metaPeers, removedID, slices.Contains(raftPeers, removedID))
}
}
return nil
})
}
//
// DO NOT ADD NEW TESTS IN THIS FILE (unless to balance test times)
// Add at the end of jetstream_cluster_<n>_test.go, with <n> being the highest value.
//