Files
pubsub/server/jetstream_test.go

23424 lines
654 KiB
Go

// Copyright 2019-2026 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
package server
import (
"bytes"
"context"
crand "crypto/rand"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"math/big"
"math/rand"
"net/http"
"net/url"
"os"
"path/filepath"
"reflect"
"runtime"
"runtime/debug"
"strconv"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/nats-io/jwt/v2"
"github.com/hanzoai/pubsub/server/sysmem"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"github.com/nats-io/nkeys"
"github.com/nats-io/nuid"
)
func TestJetStreamBasicNilConfig(t *testing.T) {
s := RunRandClientPortServer(t)
defer s.Shutdown()
if err := s.EnableJetStream(nil); err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if !s.JetStreamEnabled() {
t.Fatalf("Expected JetStream to be enabled")
}
if s.SystemAccount() == nil {
t.Fatalf("Expected system account to be created automatically")
}
// Grab our config since it was dynamically generated.
config := s.JetStreamConfig()
if config == nil {
t.Fatalf("Expected non-nil config")
}
// Check dynamic max memory.
hwMem := sysmem.Memory()
if hwMem != 0 {
// Check if memory being limited via GOMEMLIMIT if being set.
if gml := debug.SetMemoryLimit(-1); gml != math.MaxInt64 {
hwMem = gml
}
// Make sure its about 75%
est := hwMem / 4 * 3
if config.MaxMemory != est {
t.Fatalf("Expected memory to be 80 percent of system memory, got %v vs %v", config.MaxMemory, est)
}
}
// Make sure it was created.
stat, err := os.Stat(config.StoreDir)
if err != nil {
t.Fatalf("Expected the store directory to be present, %v", err)
}
if stat == nil || !stat.IsDir() {
t.Fatalf("Expected a directory")
}
}
func RunBasicJetStreamServer(t testing.TB) *Server {
opts := DefaultTestOptions
opts.Port = -1
opts.JetStream = true
opts.StoreDir = t.TempDir()
return RunServer(&opts)
}
func RunJetStreamServerOnPort(port int, sd string) *Server {
opts := DefaultTestOptions
opts.Port = port
opts.JetStream = true
opts.StoreDir = filepath.Dir(sd)
return RunServer(&opts)
}
func clientConnectToServer(t *testing.T, s *Server) *nats.Conn {
t.Helper()
nc, err := nats.Connect(s.ClientURL(),
nats.Name("JS-TEST"),
nats.ReconnectWait(5*time.Millisecond),
nats.MaxReconnects(-1))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
return nc
}
func clientConnectWithOldRequest(t *testing.T, s *Server) *nats.Conn {
nc, err := nats.Connect(s.ClientURL(), nats.UseOldRequestStyle())
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
return nc
}
func TestJetStreamEnableAndDisableAccount(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Global in simple setup should be enabled already.
if !s.GlobalAccount().JetStreamEnabled() {
t.Fatalf("Expected to have jetstream enabled on global account")
}
if na := s.JetStreamNumAccounts(); na != 1 {
t.Fatalf("Expected 1 account, got %d", na)
}
if err := s.GlobalAccount().DisableJetStream(); err != nil {
t.Fatalf("Did not expect error on disabling account: %v", err)
}
if na := s.JetStreamNumAccounts(); na != 0 {
t.Fatalf("Expected no accounts, got %d", na)
}
// Make sure we unreserved resources.
if rm, rd, err := s.JetStreamReservedResources(); err != nil {
t.Fatalf("Unexpected error requesting jetstream reserved resources: %v", err)
} else if rm != 0 || rd != 0 {
t.Fatalf("Expected reserved memory and store to be 0, got %v and %v", friendlyBytes(rm), friendlyBytes(rd))
}
acc, _ := s.LookupOrRegisterAccount("$FOO")
if err := acc.EnableJetStream(nil, nil); err != nil {
t.Fatalf("Did not expect error on enabling account: %v", err)
}
if na := s.JetStreamNumAccounts(); na != 1 {
t.Fatalf("Expected 1 account, got %d", na)
}
if err := acc.DisableJetStream(); err != nil {
t.Fatalf("Did not expect error on disabling account: %v", err)
}
if na := s.JetStreamNumAccounts(); na != 0 {
t.Fatalf("Expected no accounts, got %d", na)
}
// We should get error if disabling something not enabled.
acc, _ = s.LookupOrRegisterAccount("$BAR")
if err := acc.DisableJetStream(); err == nil {
t.Fatalf("Expected error on disabling account that was not enabled")
}
// Should get an error for trying to enable a non-registered account.
acc = NewAccount("$BAZ")
if err := acc.EnableJetStream(nil, nil); err == nil {
t.Fatalf("Expected error on enabling account that was not registered")
}
}
func TestJetStreamAddStream(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{name: "MemoryStore",
mconfig: &StreamConfig{
Name: "foo",
Retention: LimitsPolicy,
MaxAge: time.Hour,
Storage: MemoryStorage,
Replicas: 1,
}},
{name: "FileStore",
mconfig: &StreamConfig{
Name: "foo",
Retention: LimitsPolicy,
MaxAge: time.Hour,
Storage: FileStorage,
Replicas: 1,
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, js := jsClientConnect(t, s)
defer nc.Close()
js.Publish("foo", []byte("Hello World!"))
state := mset.state()
if state.Msgs != 1 {
t.Fatalf("Expected 1 message, got %d", state.Msgs)
}
if state.Bytes == 0 {
t.Fatalf("Expected non-zero bytes")
}
js.Publish("foo", []byte("Hello World Again!"))
state = mset.state()
if state.Msgs != 2 {
t.Fatalf("Expected 2 messages, got %d", state.Msgs)
}
if err := mset.delete(); err != nil {
t.Fatalf("Got an error deleting the stream: %v", err)
}
})
}
}
func TestJetStreamAddStreamDiscardNew(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{name: "MemoryStore",
mconfig: &StreamConfig{
Name: "foo",
MaxMsgs: 10,
MaxBytes: 4096,
Discard: DiscardNew,
Storage: MemoryStorage,
Replicas: 1,
}},
{name: "FileStore",
mconfig: &StreamConfig{
Name: "foo",
MaxMsgs: 10,
MaxBytes: 4096,
Discard: DiscardNew,
Storage: FileStorage,
Replicas: 1,
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
subj := "foo"
toSend := 10
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, subj, fmt.Sprintf("MSG: %d", i+1))
}
// We expect this one to fail due to discard policy.
resp, _ := nc.Request(subj, []byte("discard me"), 100*time.Millisecond)
if resp == nil {
t.Fatalf("No response, possible timeout?")
}
if pa := getPubAckResponse(resp.Data); pa == nil || pa.Error.Description != "maximum messages exceeded" || pa.Stream != "foo" {
t.Fatalf("Expected to get an error about maximum messages, got %q", resp.Data)
}
// Now do bytes.
mset.purge(nil)
big := make([]byte, 8192)
resp, _ = nc.Request(subj, big, 100*time.Millisecond)
if resp == nil {
t.Fatalf("No response, possible timeout?")
}
if pa := getPubAckResponse(resp.Data); pa == nil || pa.Error.Description != "maximum bytes exceeded" || pa.Stream != "foo" {
t.Fatalf("Expected to get an error about maximum bytes, got %q", resp.Data)
}
})
}
}
func TestJetStreamAutoTuneFSConfig(t *testing.T) {
s := RunRandClientPortServer(t)
defer s.Shutdown()
jsconfig := &JetStreamConfig{MaxMemory: -1, MaxStore: 128 * 1024 * 1024, StoreDir: t.TempDir()}
if err := s.EnableJetStream(jsconfig); err != nil {
t.Fatalf("Expected no error, got %v", err)
}
maxMsgSize := int32(512)
streamConfig := func(name string, maxMsgs, maxBytes int64) *StreamConfig {
t.Helper()
cfg := &StreamConfig{Name: name, MaxMsgSize: maxMsgSize, Storage: FileStorage}
if maxMsgs > 0 {
cfg.MaxMsgs = maxMsgs
}
if maxBytes > 0 {
cfg.MaxBytes = maxBytes
}
return cfg
}
acc := s.GlobalAccount()
testBlkSize := func(name string, maxMsgs, maxBytes int64, expectedBlkSize uint64) {
t.Helper()
mset, err := acc.addStream(streamConfig(name, maxMsgs, maxBytes))
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
fsCfg, err := mset.fileStoreConfig()
if err != nil {
t.Fatalf("Unexpected error retrieving file store: %v", err)
}
if fsCfg.BlockSize != expectedBlkSize {
t.Fatalf("Expected auto tuned block size to be %d, got %d", expectedBlkSize, fsCfg.BlockSize)
}
}
// Create a dummy stream, to ensure removing stream/account directories don't race.
_, err := acc.addStream(streamConfig("dummy", 1, 0))
require_NoError(t, err)
testBlkSize("foo", 1, 0, FileStoreMinBlkSize)
testBlkSize("foo", 1, 512, FileStoreMinBlkSize)
testBlkSize("foo", 1, 1024*1024, defaultMediumBlockSize)
testBlkSize("foo", 1, 8*1024*1024, defaultMediumBlockSize)
testBlkSize("foo_bar_baz", -1, 32*1024*1024, FileStoreMaxBlkSize)
}
func TestJetStreamPubAck(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
sname := "PUBACK"
acc := s.GlobalAccount()
mconfig := &StreamConfig{Name: sname, Subjects: []string{"foo"}, Storage: MemoryStorage}
mset, err := acc.addStream(mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
checkRespDetails := func(resp *nats.Msg, err error, seq uint64) {
if err != nil {
t.Fatalf("Unexpected error from send stream msg: %v", err)
}
if resp == nil {
t.Fatalf("No response from send stream msg")
}
pa := getPubAckResponse(resp.Data)
if pa == nil || pa.Error != nil {
t.Fatalf("Expected a valid JetStreamPubAck, got %q", resp.Data)
}
if pa.Stream != sname {
t.Fatalf("Expected %q for stream name, got %q", sname, pa.Stream)
}
if pa.Sequence != seq {
t.Fatalf("Expected %d for sequence, got %d", seq, pa.Sequence)
}
}
// Send messages and make sure pubAck details are correct.
for i := uint64(1); i <= 1000; i++ {
resp, err := nc.Request("foo", []byte("HELLO"), 100*time.Millisecond)
checkRespDetails(resp, err, i)
}
}
func TestJetStreamNextReqFromMsg(t *testing.T) {
bef := time.Now()
expires, _, _, _, _, _, _, err := nextReqFromMsg([]byte(`{"expires":5000000000}`)) // nanoseconds
require_NoError(t, err)
now := time.Now()
if expires.Before(bef.Add(5*time.Second)) || expires.After(now.Add(5*time.Second)) {
t.Fatal("Expires out of expected range")
}
}
func TestJetStreamNoPanicOnRaceBetweenShutdownAndConsumerDelete(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_STREAM", Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "MY_STREAM", Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
var cons []*consumer
for i := 0; i < 100; i++ {
o, err := mset.addConsumer(&ConsumerConfig{
Durable: fmt.Sprintf("d%d", i),
AckPolicy: AckExplicit,
})
require_NoError(t, err)
defer o.delete()
cons = append(cons, o)
}
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
for _, c := range cons {
c.delete()
}
}()
time.Sleep(10 * time.Millisecond)
s.Shutdown()
})
}
}
func TestJetStreamAddStreamMaxMsgSize(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{name: "MemoryStore",
mconfig: &StreamConfig{
Name: "foo",
Retention: LimitsPolicy,
MaxAge: time.Hour,
Storage: MemoryStorage,
MaxMsgSize: 22,
Replicas: 1,
}},
{name: "FileStore",
mconfig: &StreamConfig{
Name: "foo",
Retention: LimitsPolicy,
MaxAge: time.Hour,
Storage: FileStorage,
MaxMsgSize: 22,
Replicas: 1,
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
if _, err := nc.Request("foo", []byte("Hello World!"), time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
tooBig := []byte("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ")
resp, err := nc.Request("foo", tooBig, time.Second)
require_NoError(t, err)
if pa := getPubAckResponse(resp.Data); pa == nil || pa.Error.Description != "message size exceeds maximum allowed" {
t.Fatalf("Expected to get an error for maximum message size, got %q", pa.Error)
}
})
}
}
func TestJetStreamAddStreamCanonicalNames(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
acc := s.GlobalAccount()
expectErr := func(_ *stream, err error) {
t.Helper()
if !IsNatsErr(err, JSStreamInvalidConfigF) {
t.Fatalf("Expected error but got none")
}
}
expectErr(acc.addStream(&StreamConfig{Name: "foo.bar"}))
expectErr(acc.addStream(&StreamConfig{Name: "foo.bar."}))
expectErr(acc.addStream(&StreamConfig{Name: "foo.*"}))
expectErr(acc.addStream(&StreamConfig{Name: "foo.>"}))
expectErr(acc.addStream(&StreamConfig{Name: "*"}))
expectErr(acc.addStream(&StreamConfig{Name: ">"}))
expectErr(acc.addStream(&StreamConfig{Name: "*>"}))
}
func TestJetStreamAddStreamBadSubjects(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc := clientConnectToServer(t, s)
defer nc.Close()
expectAPIErr := func(cfg StreamConfig) {
t.Helper()
req, err := json.Marshal(cfg)
require_NoError(t, err)
resp, _ := nc.Request(fmt.Sprintf(JSApiStreamCreateT, cfg.Name), req, time.Second)
var scResp JSApiStreamCreateResponse
if err := json.Unmarshal(resp.Data, &scResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
require_Error(t, scResp.ToError(), NewJSStreamInvalidConfigError(fmt.Errorf("invalid subject")))
}
expectAPIErr(StreamConfig{Name: "MyStream", Storage: MemoryStorage, Subjects: []string{"foo.bar."}})
expectAPIErr(StreamConfig{Name: "MyStream", Storage: MemoryStorage, Subjects: []string{".."}})
expectAPIErr(StreamConfig{Name: "MyStream", Storage: MemoryStorage, Subjects: []string{".*"}})
expectAPIErr(StreamConfig{Name: "MyStream", Storage: MemoryStorage, Subjects: []string{".>"}})
expectAPIErr(StreamConfig{Name: "MyStream", Storage: MemoryStorage, Subjects: []string{" x"}})
expectAPIErr(StreamConfig{Name: "MyStream", Storage: MemoryStorage, Subjects: []string{"y "}})
}
func TestJetStreamMaxConsumers(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "MAXC",
Storage: nats.MemoryStorage,
Subjects: []string{"in.maxc.>"},
MaxConsumers: 2,
}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
si, err := js.StreamInfo("MAXC")
require_NoError(t, err)
if si.Config.MaxConsumers != 2 {
t.Fatalf("Expected max of 2, got %d", si.Config.MaxConsumers)
}
// Make sure we get the right error.
// This should succeed.
if _, err := js.PullSubscribe("in.maxc.foo", "maxc_foo"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Create for the same consumer must be idempotent, and not trigger limit.
if _, err := js.PullSubscribe("in.maxc.foo", "maxc_foo"); 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: "MAXC",
Config: ConsumerConfig{Durable: "maxc_baz"},
Action: ActionCreate,
}
req, err := json.Marshal(obsReq)
require_NoError(t, err)
msg, err := nc.Request(fmt.Sprintf(JSApiDurableCreateT, "MAXC", "maxc_baz"), 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, "MAXC", "maxc_baz"), 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)
}
// Exceeds limit.
if _, err := js.SubscribeSync("in.maxc.bar"); err == nil {
t.Fatalf("Expected error but got none")
}
}
func TestJetStreamAddStreamOverlappingSubjects(t *testing.T) {
mconfig := &StreamConfig{
Name: "ok",
Storage: MemoryStorage,
Subjects: []string{"foo", "bar", "baz.*", "foo.bar.baz.>"},
}
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
acc := s.GlobalAccount()
mset, err := acc.addStream(mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
expectErr := func(_ *stream, err error) {
t.Helper()
if err == nil || !strings.Contains(err.Error(), "subjects overlap") {
t.Fatalf("Expected error but got none")
}
}
// Test that any overlapping subjects will fail.
expectErr(acc.addStream(&StreamConfig{Name: "foo"}))
expectErr(acc.addStream(&StreamConfig{Name: "a", Subjects: []string{"baz", "bar"}}))
expectErr(acc.addStream(&StreamConfig{Name: "b", Subjects: []string{">"}, NoAck: true}))
expectErr(acc.addStream(&StreamConfig{Name: "c", Subjects: []string{"baz.33"}}))
// Using NoAck on the following because technically they overlap with the $JS.> namespace...
expectErr(acc.addStream(&StreamConfig{Name: "d", Subjects: []string{"*.33"}, NoAck: true}))
expectErr(acc.addStream(&StreamConfig{Name: "e", Subjects: []string{"*.>"}, NoAck: true}))
expectErr(acc.addStream(&StreamConfig{Name: "f", Subjects: []string{"foo.bar", "*.bar.>"}, NoAck: true}))
}
func TestJetStreamAddStreamOverlapWithJSAPISubjects(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
acc := s.GlobalAccount()
expectNoErr := func(_ *stream, err error) {
t.Helper()
switch {
case err == nil:
default:
t.Errorf("Unexpected error: %v", err)
}
}
expectErr := func(_ *stream, err error) {
t.Helper()
switch {
case err == nil:
t.Errorf("Expected error but got none")
case !strings.Contains(err.Error(), "subjects that overlap with jetstream api"):
case !strings.Contains(err.Error(), "subjects that overlap with system api"):
case !strings.Contains(err.Error(), "capturing all subjects requires no-ack to be true"):
default:
t.Errorf("Unexpected error: %v", err)
}
}
// Test that any overlapping subjects with our JSAPI should fail.
expectErr(acc.addStream(&StreamConfig{Name: "a", Subjects: []string{"$JS.API.foo", "$JS.API.bar"}}))
expectErr(acc.addStream(&StreamConfig{Name: "b", Subjects: []string{"$JS.API.>"}}))
expectErr(acc.addStream(&StreamConfig{Name: "c", Subjects: []string{"$JS.API.*"}}))
expectErr(acc.addStream(&StreamConfig{Name: "d", Subjects: []string{"$JS.>"}}))
expectErr(acc.addStream(&StreamConfig{Name: "e", Subjects: []string{"$SYS.>"}}))
expectErr(acc.addStream(&StreamConfig{Name: "f", Subjects: []string{"*.>"}}))
expectErr(acc.addStream(&StreamConfig{Name: "g", Subjects: []string{">"}}))
// Events and advisories should be OK.
expectNoErr(acc.addStream(&StreamConfig{Name: "h", Subjects: []string{"$JS.EVENT.>"}}))
expectNoErr(acc.addStream(&StreamConfig{Name: "i", Subjects: []string{"$SYS.ACCOUNT.>"}}))
// So should a full wild-card with NoAck, but need to clean up overlapping streams first.
for _, name := range []string{"h", "i"} {
mset, err := acc.lookupStream(name)
require_NoError(t, err)
require_NoError(t, mset.delete())
}
expectNoErr(acc.addStream(&StreamConfig{Name: "j", Subjects: []string{">"}, NoAck: true}))
}
func TestJetStreamAddStreamSameConfigOK(t *testing.T) {
mconfig := &StreamConfig{
Name: "ok",
Subjects: []string{"foo", "bar", "baz.*", "foo.bar.baz.>"},
Storage: MemoryStorage,
}
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
acc := s.GlobalAccount()
mset, err := acc.addStream(mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
// Adding again with same config should be idempotent.
if _, err = acc.addStream(mconfig); err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
}
func sendStreamMsg(t *testing.T, nc *nats.Conn, subject, msg string) *PubAck {
t.Helper()
resp, err := nc.Request(subject, []byte(msg), 500*time.Millisecond)
if resp == nil {
t.Fatalf("No response for %q (error: %v)", msg, err)
}
pa := getPubAckResponse(resp.Data)
if pa == nil || pa.Error != nil {
t.Fatalf("Expected a valid JetStreamPubAck, got %q", resp.Data)
}
return pa.PubAck
}
func TestJetStreamBasicAckPublish(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "foo", Storage: MemoryStorage, Subjects: []string{"foo.*"}}},
{"FileStore", &StreamConfig{Name: "foo", Storage: FileStorage, Subjects: []string{"foo.*"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
for i := 0; i < 50; i++ {
sendStreamMsg(t, nc, "foo.bar", "Hello World!")
}
state := mset.state()
if state.Msgs != 50 {
t.Fatalf("Expected 50 messages, got %d", state.Msgs)
}
})
}
}
func TestJetStreamStateTimestamps(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "foo", Storage: MemoryStorage, Subjects: []string{"foo.*"}}},
{"FileStore", &StreamConfig{Name: "foo", Storage: FileStorage, Subjects: []string{"foo.*"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
start := time.Now()
delay := 250 * time.Millisecond
sendStreamMsg(t, nc, "foo.bar", "Hello World!")
time.Sleep(delay)
sendStreamMsg(t, nc, "foo.bar", "Hello World Again!")
state := mset.state()
if state.FirstTime.Before(start) {
t.Fatalf("Unexpected first message timestamp: %v", state.FirstTime)
}
if state.LastTime.Before(start.Add(delay)) {
t.Fatalf("Unexpected last message timestamp: %v", state.LastTime)
}
})
}
}
func TestJetStreamNoAckStream(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "foo", Storage: MemoryStorage, NoAck: true}},
{"FileStore", &StreamConfig{Name: "foo", Storage: FileStorage, NoAck: true}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// We can use NoAck to suppress acks even when reply subjects are present.
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
if _, err := nc.Request("foo", []byte("Hello World!"), 25*time.Millisecond); err != nats.ErrTimeout {
t.Fatalf("Expected a timeout error and no response with acks suppressed")
}
state := mset.state()
if state.Msgs != 1 {
t.Fatalf("Expected 1 message, got %d", state.Msgs)
}
})
}
}
func TestJetStreamBasicDeliverSubject(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MSET", Storage: MemoryStorage, Subjects: []string{"foo.*"}}},
{"FileStore", &StreamConfig{Name: "MSET", Storage: FileStorage, Subjects: []string{"foo.*"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
toSend := 100
sendSubj := "foo.bar"
for i := 1; i <= toSend; i++ {
sendStreamMsg(t, nc, sendSubj, strconv.Itoa(i))
}
state := mset.state()
if state.Msgs != uint64(toSend) {
t.Fatalf("Expected %d messages, got %d", toSend, state.Msgs)
}
// Now create an consumer. Use different connection.
nc2 := clientConnectToServer(t, s)
defer nc2.Close()
sub, _ := nc2.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc2.Flush()
o, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: sub.Subject})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
// Check for our messages.
checkMsgs := func(seqOff int) {
t.Helper()
checkFor(t, 250*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != toSend {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, toSend)
}
return nil
})
// Now let's check the messages
for i := 0; i < toSend; i++ {
m, _ := sub.NextMsg(time.Second)
// JetStream will have the subject match the stream subject, not delivery subject.
if m.Subject != sendSubj {
t.Fatalf("Expected original subject of %q, but got %q", sendSubj, m.Subject)
}
// Now check that reply subject exists and has a sequence as the last token.
if seq := o.seqFromReply(m.Reply); seq != uint64(i+seqOff) {
t.Fatalf("Expected sequence of %d , got %d", i+seqOff, seq)
}
// Ack the message here.
m.Respond(nil)
}
}
checkMsgs(1)
// Now send more and make sure delivery picks back up.
for i := toSend + 1; i <= toSend*2; i++ {
sendStreamMsg(t, nc, sendSubj, strconv.Itoa(i))
}
state = mset.state()
if state.Msgs != uint64(toSend*2) {
t.Fatalf("Expected %d messages, got %d", toSend*2, state.Msgs)
}
checkMsgs(101)
checkSubEmpty := func() {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != 0 {
t.Fatalf("Expected sub to have no pending")
}
}
checkSubEmpty()
o.delete()
// Now check for deliver last, deliver new and deliver by seq.
o, err = mset.addConsumer(&ConsumerConfig{DeliverSubject: sub.Subject, DeliverPolicy: DeliverLast})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Did not get expected message, got %v", err)
}
// All Consumers start with sequence #1.
if seq := o.seqFromReply(m.Reply); seq != 1 {
t.Fatalf("Expected sequence to be 1, but got %d", seq)
}
// Check that is the last msg we sent though.
if mseq, _ := strconv.Atoi(string(m.Data)); mseq != 200 {
t.Fatalf("Expected message sequence to be 200, but got %d", mseq)
}
checkSubEmpty()
o.delete()
// Make sure we only got one message.
if m, err := sub.NextMsg(5 * time.Millisecond); err == nil {
t.Fatalf("Expected no msg, got %+v", m)
}
checkSubEmpty()
o.delete()
// Now try by sequence number.
o, err = mset.addConsumer(&ConsumerConfig{DeliverSubject: sub.Subject, DeliverPolicy: DeliverByStartSequence, OptStartSeq: 101})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
checkMsgs(1)
// Now do push based queue-subscribers
sub, _ = nc2.QueueSubscribeSync("_qg_", "dev")
defer sub.Unsubscribe()
nc2.Flush()
o, err = mset.addConsumer(&ConsumerConfig{DeliverSubject: sub.Subject, DeliverGroup: "dev"})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
// Since we sent another batch need check to be looking for 2x.
toSend *= 2
checkMsgs(1)
})
}
}
func workerModeConfig(name string) *ConsumerConfig {
return &ConsumerConfig{Durable: name, AckPolicy: AckExplicit}
}
func TestJetStreamBasicWorkQueue(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_MSG_SET", Storage: MemoryStorage, Subjects: []string{"foo", "bar"}}},
{"FileStore", &StreamConfig{Name: "MY_MSG_SET", Storage: FileStorage, Subjects: []string{"foo", "bar"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
// Create basic work queue mode consumer.
oname := "WQ"
o, err := mset.addConsumer(workerModeConfig(oname))
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
if o.nextSeq() != 1 {
t.Fatalf("Expected to be starting at sequence 1")
}
nc := clientConnectWithOldRequest(t, s)
defer nc.Close()
// Now load up some messages.
toSend := 100
sendSubj := "bar"
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, sendSubj, "Hello World!")
}
state := mset.state()
if state.Msgs != uint64(toSend) {
t.Fatalf("Expected %d messages, got %d", toSend, state.Msgs)
}
getNext := func(seqno int) {
t.Helper()
nextMsg, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error for seq %d: %v", seqno, err)
}
if nextMsg.Subject != "bar" {
t.Fatalf("Expected subject of %q, got %q", "bar", nextMsg.Subject)
}
if seq := o.seqFromReply(nextMsg.Reply); seq != uint64(seqno) {
t.Fatalf("Expected sequence of %d , got %d", seqno, seq)
}
}
// Make sure we can get the messages already there.
for i := 1; i <= toSend; i++ {
getNext(i)
}
// Now we want to make sure we can get a message that is published to the message
// set as we are waiting for it.
nextDelay := 50 * time.Millisecond
go func() {
time.Sleep(nextDelay)
sendStreamMsg(t, nc, sendSubj, "Hello World!")
}()
start := time.Now()
getNext(toSend + 1)
if time.Since(start) < nextDelay {
t.Fatalf("Received message too quickly")
}
// Now do same thing but combine waiting for new ones with sending.
go func() {
time.Sleep(nextDelay)
for i := 0; i < toSend; i++ {
nc.Request(sendSubj, []byte("Hello World!"), 50*time.Millisecond)
}
}()
for i := toSend + 2; i < toSend*2+2; i++ {
getNext(i)
}
})
}
}
func TestJetStreamWorkQueueMaxWaiting(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_MSG_SET", Storage: MemoryStorage, Subjects: []string{"foo", "bar"}}},
{"FileStore", &StreamConfig{Name: "MY_MSG_SET", Storage: FileStorage, Subjects: []string{"foo", "bar"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
// Make sure these cases fail
cfg := &ConsumerConfig{Durable: "foo", AckPolicy: AckExplicit, MaxWaiting: 10, DeliverSubject: "_INBOX.22"}
if _, err := mset.addConsumer(cfg); err == nil {
t.Fatalf("Expected an error with MaxWaiting set on non-pull based consumer")
}
// Create basic work queue mode consumer.
wcfg := workerModeConfig("MAXWQ")
o, err := mset.addConsumer(wcfg)
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
// Make sure we set default correctly.
if cfg := o.config(); cfg.MaxWaiting != JSWaitQueueDefaultMax {
t.Fatalf("Expected default max waiting to have been set to %d, got %d", JSWaitQueueDefaultMax, cfg.MaxWaiting)
}
expectWaiting := func(expected int) {
t.Helper()
checkFor(t, time.Second, 25*time.Millisecond, func() error {
if oi := o.info(); oi.NumWaiting != expected {
return fmt.Errorf("Expected %d waiting, got %d", expected, oi.NumWaiting)
}
return nil
})
}
nc := clientConnectWithOldRequest(t, s)
defer nc.Close()
// Like muxed new INBOX.
sub, _ := nc.SubscribeSync("req.*")
defer sub.Unsubscribe()
nc.Flush()
checkSubPending := func(numExpected int) {
t.Helper()
checkFor(t, 200*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, err := sub.Pending(); err != nil || nmsgs != numExpected {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, numExpected)
}
return nil
})
}
getSubj := o.requestNextMsgSubject()
// Queue up JSWaitQueueDefaultMax requests.
for i := 0; i < JSWaitQueueDefaultMax; i++ {
nc.PublishRequest(getSubj, fmt.Sprintf("req.%d", i), nil)
}
expectWaiting(JSWaitQueueDefaultMax)
// We are at the max, so we should get a 409 saying that we have
// exceeded the number of pull requests.
m, err := nc.Request(getSubj, nil, 100*time.Millisecond)
require_NoError(t, err)
// Make sure this is the 409
if v := m.Header.Get("Status"); v != "409" {
t.Fatalf("Expected a 409 status code, got %q", v)
}
// The sub for the other requests should not have received anything
checkSubPending(0)
// Now send some messages that should make some of the requests complete
sendStreamMsg(t, nc, "foo", "Hello World!")
sendStreamMsg(t, nc, "bar", "Hello World!")
expectWaiting(JSWaitQueueDefaultMax - 2)
})
}
}
func TestJetStreamWorkQueueWrapWaiting(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_MSG_SET", Storage: MemoryStorage, Subjects: []string{"foo", "bar"}}},
{"FileStore", &StreamConfig{Name: "MY_MSG_SET", Storage: FileStorage, Subjects: []string{"foo", "bar"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
maxWaiting := 8
wcfg := workerModeConfig("WRAP")
wcfg.MaxWaiting = maxWaiting
o, err := mset.addConsumer(wcfg)
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
getSubj := o.requestNextMsgSubject()
expectWaiting := func(expected int) {
t.Helper()
checkFor(t, time.Second, 25*time.Millisecond, func() error {
if oi := o.info(); oi.NumWaiting != expected {
return fmt.Errorf("Expected %d waiting, got %d", expected, oi.NumWaiting)
}
return nil
})
}
nc := clientConnectToServer(t, s)
defer nc.Close()
sub, _ := nc.SubscribeSync("req.*")
defer sub.Unsubscribe()
nc.Flush()
// Fill up waiting.
for i := 0; i < maxWaiting; i++ {
nc.PublishRequest(getSubj, fmt.Sprintf("req.%d", i), nil)
}
expectWaiting(maxWaiting)
// Now use 1/2 of the waiting.
for i := 0; i < maxWaiting/2; i++ {
sendStreamMsg(t, nc, "foo", "Hello World!")
}
expectWaiting(maxWaiting / 2)
// Now add in two (2) more pull requests.
for i := maxWaiting; i < maxWaiting+2; i++ {
nc.PublishRequest(getSubj, fmt.Sprintf("req.%d", i), nil)
}
expectWaiting(maxWaiting/2 + 2)
// Now use second 1/2 of the waiting and the 2 extra.
for i := 0; i < maxWaiting/2+2; i++ {
sendStreamMsg(t, nc, "bar", "Hello World!")
}
expectWaiting(0)
checkFor(t, 200*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != maxWaiting+2 {
return fmt.Errorf("Expected sub to have %d pending, got %d", maxWaiting+2, nmsgs)
}
return nil
})
})
}
}
func TestJetStreamWorkQueueRequest(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_MSG_SET", Storage: MemoryStorage, Subjects: []string{"foo", "bar"}}},
{"FileStore", &StreamConfig{Name: "MY_MSG_SET", Storage: FileStorage, Subjects: []string{"foo", "bar"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
o, err := mset.addConsumer(workerModeConfig("WRAP"))
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
toSend := 25
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "bar", "Hello World!")
}
reply := "_.consumer._"
sub, _ := nc.SubscribeSync(reply)
defer sub.Unsubscribe()
getSubj := o.requestNextMsgSubject()
checkSubPending := func(numExpected int) {
t.Helper()
checkFor(t, 200*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != numExpected {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, numExpected)
}
return nil
})
}
// Create a formal request object.
req := &JSApiConsumerGetNextRequest{Batch: toSend}
jreq, _ := json.Marshal(req)
nc.PublishRequest(getSubj, reply, jreq)
checkSubPending(toSend)
// Now check that we can ask for NoWait
req.Batch = 1
req.NoWait = true
jreq, _ = json.Marshal(req)
resp, err := nc.Request(getSubj, jreq, 100*time.Millisecond)
require_NoError(t, err)
if status := resp.Header.Get("Status"); !strings.HasPrefix(status, "404") {
t.Fatalf("Expected status code of 404")
}
// Load up more messages.
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "foo", "Hello World!")
}
// Now we will ask for a batch larger then what is queued up.
req.Batch = toSend + 10
req.NoWait = true
jreq, _ = json.Marshal(req)
nc.PublishRequest(getSubj, reply, jreq)
// We should now have 2 * toSend + the 404 message.
checkSubPending(2*toSend + 1)
for i := 0; i < 2*toSend+1; i++ {
sub.NextMsg(time.Millisecond)
}
checkSubPending(0)
mset.purge(nil)
// Now do expiration
req.Batch = 1
req.NoWait = false
req.Expires = 100 * time.Millisecond
jreq, _ = json.Marshal(req)
nc.PublishRequest(getSubj, reply, jreq)
// Let it expire
time.Sleep(200 * time.Millisecond)
// Send a few more messages. These should not be delivered to the sub.
sendStreamMsg(t, nc, "foo", "Hello World!")
sendStreamMsg(t, nc, "bar", "Hello World!")
time.Sleep(100 * time.Millisecond)
// Expect the request timed out message.
checkSubPending(1)
if resp, _ = sub.NextMsg(time.Millisecond); resp == nil {
t.Fatalf("Expected an expired status message")
}
if status := resp.Header.Get("Status"); !strings.HasPrefix(status, "408") {
t.Fatalf("Expected status code of 408")
}
// Send a new request, we should not get the 408 because our previous request
// should have expired.
nc.PublishRequest(getSubj, reply, jreq)
checkSubPending(1)
sub.NextMsg(time.Second)
checkSubPending(0)
})
}
}
func TestJetStreamSubjectFiltering(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MSET", Storage: MemoryStorage, Subjects: []string{"foo.*"}}},
{"FileStore", &StreamConfig{Name: "MSET", Storage: FileStorage, Subjects: []string{"foo.*"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
toSend := 50
subjA := "foo.A"
subjB := "foo.B"
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, subjA, "Hello World!")
sendStreamMsg(t, nc, subjB, "Hello World!")
}
state := mset.state()
if state.Msgs != uint64(toSend*2) {
t.Fatalf("Expected %d messages, got %d", toSend*2, state.Msgs)
}
delivery := nats.NewInbox()
sub, _ := nc.SubscribeSync(delivery)
defer sub.Unsubscribe()
nc.Flush()
o, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: delivery, FilterSubject: subjB})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
// Now let's check the messages
for i := 1; i <= toSend; i++ {
m, err := sub.NextMsg(time.Second)
require_NoError(t, err)
// JetStream will have the subject match the stream subject, not delivery subject.
// We want these to only be subjB.
if m.Subject != subjB {
t.Fatalf("Expected original subject of %q, but got %q", subjB, m.Subject)
}
// Now check that reply subject exists and has a sequence as the last token.
if seq := o.seqFromReply(m.Reply); seq != uint64(i) {
t.Fatalf("Expected sequence of %d , got %d", i, seq)
}
// Ack the message here.
m.Respond(nil)
}
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != 0 {
t.Fatalf("Expected sub to have no pending")
}
})
}
}
func TestJetStreamWorkQueueSubjectFiltering(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_MSG_SET", Storage: MemoryStorage, Subjects: []string{"foo.*"}}},
{"FileStore", &StreamConfig{Name: "MY_MSG_SET", Storage: FileStorage, Subjects: []string{"foo.*"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
toSend := 50
subjA := "foo.A"
subjB := "foo.B"
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, subjA, "Hello World!")
sendStreamMsg(t, nc, subjB, "Hello World!")
}
state := mset.state()
if state.Msgs != uint64(toSend*2) {
t.Fatalf("Expected %d messages, got %d", toSend*2, state.Msgs)
}
oname := "WQ"
o, err := mset.addConsumer(&ConsumerConfig{Durable: oname, FilterSubject: subjA, AckPolicy: AckExplicit})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
if o.nextSeq() != 1 {
t.Fatalf("Expected to be starting at sequence 1")
}
getNext := func(seqno int) {
t.Helper()
nextMsg, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second)
require_NoError(t, err)
if nextMsg.Subject != subjA {
t.Fatalf("Expected subject of %q, got %q", subjA, nextMsg.Subject)
}
if seq := o.seqFromReply(nextMsg.Reply); seq != uint64(seqno) {
t.Fatalf("Expected sequence of %d , got %d", seqno, seq)
}
nextMsg.Respond(nil)
}
// Make sure we can get the messages already there.
for i := 1; i <= toSend; i++ {
getNext(i)
}
})
}
}
func TestJetStreamWildcardSubjectFiltering(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "ORDERS", Storage: MemoryStorage, Subjects: []string{"orders.*.*"}}},
{"FileStore", &StreamConfig{Name: "ORDERS", Storage: FileStorage, Subjects: []string{"orders.*.*"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
toSend := 100
for i := 1; i <= toSend; i++ {
subj := fmt.Sprintf("orders.%d.%s", i, "NEW")
sendStreamMsg(t, nc, subj, "new order")
}
// Randomly move 25 to shipped.
toShip := 25
shipped := make(map[int]bool)
for i := 0; i < toShip; {
orderId := rand.Intn(toSend-1) + 1
if shipped[orderId] {
continue
}
subj := fmt.Sprintf("orders.%d.%s", orderId, "SHIPPED")
sendStreamMsg(t, nc, subj, "shipped order")
shipped[orderId] = true
i++
}
state := mset.state()
if state.Msgs != uint64(toSend+toShip) {
t.Fatalf("Expected %d messages, got %d", toSend+toShip, state.Msgs)
}
delivery := nats.NewInbox()
sub, _ := nc.SubscribeSync(delivery)
defer sub.Unsubscribe()
nc.Flush()
// Get all shipped.
o, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: delivery, FilterSubject: "orders.*.SHIPPED"})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
checkFor(t, time.Second, 25*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != toShip {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, toShip)
}
return nil
})
for nmsgs, _, _ := sub.Pending(); nmsgs > 0; nmsgs, _, _ = sub.Pending() {
sub.NextMsg(time.Second)
}
if nmsgs, _, _ := sub.Pending(); nmsgs != 0 {
t.Fatalf("Expected no pending, got %d", nmsgs)
}
// Get all new
o, err = mset.addConsumer(&ConsumerConfig{DeliverSubject: delivery, FilterSubject: "orders.*.NEW"})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
checkFor(t, time.Second, 25*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != toSend {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, toSend)
}
return nil
})
for nmsgs, _, _ := sub.Pending(); nmsgs > 0; nmsgs, _, _ = sub.Pending() {
sub.NextMsg(time.Second)
}
if nmsgs, _, _ := sub.Pending(); nmsgs != 0 {
t.Fatalf("Expected no pending, got %d", nmsgs)
}
// Now grab a single orderId that has shipped, so we should have two messages.
var orderId int
for orderId = range shipped {
break
}
subj := fmt.Sprintf("orders.%d.*", orderId)
o, err = mset.addConsumer(&ConsumerConfig{DeliverSubject: delivery, FilterSubject: subj})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
checkFor(t, time.Second, 25*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != 2 {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, 2)
}
return nil
})
})
}
}
func TestJetStreamWorkQueueAckAndNext(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_MSG_SET", Storage: MemoryStorage, Subjects: []string{"foo", "bar"}}},
{"FileStore", &StreamConfig{Name: "MY_MSG_SET", Storage: FileStorage, Subjects: []string{"foo", "bar"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
// Create basic work queue mode consumer.
oname := "WQ"
o, err := mset.addConsumer(workerModeConfig(oname))
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
if o.nextSeq() != 1 {
t.Fatalf("Expected to be starting at sequence 1")
}
nc := clientConnectToServer(t, s)
defer nc.Close()
// Now load up some messages.
toSend := 100
sendSubj := "bar"
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, sendSubj, "Hello World!")
}
state := mset.state()
if state.Msgs != uint64(toSend) {
t.Fatalf("Expected %d messages, got %d", toSend, state.Msgs)
}
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
// Kick things off.
// For normal work queue semantics, you send requests to the subject with stream and consumer name.
// We will do this to start it off then use ack+next to get other messages.
nc.PublishRequest(o.requestNextMsgSubject(), sub.Subject, nil)
for i := 0; i < toSend; i++ {
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Unexpected error waiting for messages: %v", err)
}
if !bytes.Equal(m.Data, []byte("Hello World!")) {
t.Fatalf("Got an invalid message from the stream: %q", m.Data)
}
nc.PublishRequest(m.Reply, sub.Subject, AckNext)
}
})
}
}
func TestJetStreamWorkQueueRequestBatch(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_MSG_SET", Storage: MemoryStorage, Subjects: []string{"foo", "bar"}}},
{"FileStore", &StreamConfig{Name: "MY_MSG_SET", Storage: FileStorage, Subjects: []string{"foo", "bar"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
// Create basic work queue mode consumer.
oname := "WQ"
o, err := mset.addConsumer(workerModeConfig(oname))
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
if o.nextSeq() != 1 {
t.Fatalf("Expected to be starting at sequence 1")
}
nc := clientConnectToServer(t, s)
defer nc.Close()
// Now load up some messages.
toSend := 100
sendSubj := "bar"
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, sendSubj, "Hello World!")
}
state := mset.state()
if state.Msgs != uint64(toSend) {
t.Fatalf("Expected %d messages, got %d", toSend, state.Msgs)
}
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
// For normal work queue semantics, you send requests to the subject with stream and consumer name.
// We will do this to start it off then use ack+next to get other messages.
// Kick things off with batch size of 50.
batchSize := 50
nc.PublishRequest(o.requestNextMsgSubject(), sub.Subject, []byte(strconv.Itoa(batchSize)))
// We should receive batchSize with no acks or additional requests.
checkFor(t, 250*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != batchSize {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, batchSize)
}
return nil
})
// Now queue up the request without messages and add them after.
sub, _ = nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
mset.purge(nil)
nc.PublishRequest(o.requestNextMsgSubject(), sub.Subject, []byte(strconv.Itoa(batchSize)))
nc.Flush() // Make sure its registered.
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, sendSubj, "Hello World!")
}
// We should receive batchSize with no acks or additional requests.
checkFor(t, 250*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != batchSize {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, batchSize)
}
return nil
})
})
}
}
func TestJetStreamWorkQueueRetentionStream(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{name: "MemoryStore", mconfig: &StreamConfig{
Name: "MWQ",
Storage: MemoryStorage,
Subjects: []string{"MY_WORK_QUEUE.>"},
Retention: WorkQueuePolicy},
},
{name: "FileStore", mconfig: &StreamConfig{
Name: "MWQ",
Storage: FileStorage,
Subjects: []string{"MY_WORK_QUEUE.>"},
Retention: WorkQueuePolicy},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
// This type of stream has restrictions which we will test here.
// DeliverAll is only start mode allowed.
if _, err := mset.addConsumer(&ConsumerConfig{DeliverPolicy: DeliverLast}); err == nil {
t.Fatalf("Expected an error with anything but DeliverAll")
}
// We will create a non-partitioned consumer. This should succeed.
o, err := mset.addConsumer(&ConsumerConfig{Durable: "PBO", AckPolicy: AckExplicit})
require_NoError(t, err)
defer o.delete()
// Now if we create another this should fail, only can have one non-partitioned.
if _, err := mset.addConsumer(&ConsumerConfig{}); err == nil {
t.Fatalf("Expected an error on attempt for second consumer for a workqueue")
}
o.delete()
if numo := mset.numConsumers(); numo != 0 {
t.Fatalf("Expected to have zero consumers, got %d", numo)
}
// Now add in an consumer that has a partition.
pindex := 1
pConfig := func(pname string) *ConsumerConfig {
dname := fmt.Sprintf("PPBO-%d", pindex)
pindex += 1
return &ConsumerConfig{Durable: dname, FilterSubject: pname, AckPolicy: AckExplicit}
}
o, err = mset.addConsumer(pConfig("MY_WORK_QUEUE.A"))
require_NoError(t, err)
defer o.delete()
// Now creating another with separate partition should work.
o2, err := mset.addConsumer(pConfig("MY_WORK_QUEUE.B"))
require_NoError(t, err)
defer o2.delete()
// Anything that would overlap should fail though.
if _, err := mset.addConsumer(pConfig("MY_WORK_QUEUE.A")); err == nil {
t.Fatalf("Expected an error on attempt for partitioned consumer for a workqueue")
}
if _, err := mset.addConsumer(pConfig("MY_WORK_QUEUE.B")); err == nil {
t.Fatalf("Expected an error on attempt for partitioned consumer for a workqueue")
}
o3, err := mset.addConsumer(pConfig("MY_WORK_QUEUE.C"))
require_NoError(t, err)
o.delete()
o2.delete()
o3.delete()
// Test with wildcards, first from wider to narrower
o, err = mset.addConsumer(pConfig("MY_WORK_QUEUE.>"))
require_NoError(t, err)
if _, err := mset.addConsumer(pConfig("MY_WORK_QUEUE.*.BAR")); err == nil {
t.Fatalf("Expected an error on attempt for partitioned consumer for a workqueue")
}
o.delete()
// Now from narrower to wider
o, err = mset.addConsumer(pConfig("MY_WORK_QUEUE.*.BAR"))
require_NoError(t, err)
if _, err := mset.addConsumer(pConfig("MY_WORK_QUEUE.>")); err == nil {
t.Fatalf("Expected an error on attempt for partitioned consumer for a workqueue")
}
o.delete()
// Push based will be allowed now, including ephemerals.
// They can not overlap etc meaning same rules as above apply.
o4, err := mset.addConsumer(&ConsumerConfig{
Durable: "DURABLE",
DeliverSubject: "SOME.SUBJ",
AckPolicy: AckExplicit,
})
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
defer o4.delete()
// Now try to create an ephemeral
nc := clientConnectToServer(t, s)
defer nc.Close()
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc.Flush()
// This should fail at first due to conflict above.
ephCfg := &ConsumerConfig{DeliverSubject: sub.Subject, AckPolicy: AckExplicit}
if _, err := mset.addConsumer(ephCfg); err == nil {
t.Fatalf("Expected an error ")
}
// Delete of o4 should clear.
o4.delete()
o5, err := mset.addConsumer(ephCfg)
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
defer o5.delete()
})
}
}
func TestJetStreamAckAllRedelivery(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_S22", Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "MY_S22", Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Now load up some messages.
toSend := 100
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, c.mconfig.Name, "Hello World!")
}
state := mset.state()
if state.Msgs != uint64(toSend) {
t.Fatalf("Expected %d messages, got %d", toSend, state.Msgs)
}
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc.Flush()
o, err := mset.addConsumer(&ConsumerConfig{
DeliverSubject: sub.Subject,
AckWait: 50 * time.Millisecond,
AckPolicy: AckAll,
})
if err != nil {
t.Fatalf("Unexpected error adding consumer: %v", err)
}
defer o.delete()
// Wait for messages.
// We will do 5 redeliveries.
for i := 1; i <= 5; i++ {
checkFor(t, 500*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != toSend*i {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, toSend*i)
}
return nil
})
}
// Stop redeliveries.
o.delete()
// Now make sure that they are all redelivered in order for each redelivered batch.
for l := 1; l <= 5; l++ {
for i := 1; i <= toSend; i++ {
m, _ := sub.NextMsg(time.Second)
if seq := o.streamSeqFromReply(m.Reply); seq != uint64(i) {
t.Fatalf("Expected stream sequence of %d, got %d", i, seq)
}
}
}
})
}
}
func TestJetStreamAckReplyStreamPending(t *testing.T) {
msc := StreamConfig{
Name: "MY_WQ",
Subjects: []string{"foo.*"},
Storage: MemoryStorage,
MaxAge: 1 * time.Second,
Retention: WorkQueuePolicy,
}
fsc := msc
fsc.Storage = FileStorage
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &msc},
{"FileStore", &fsc},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Now load up some messages.
toSend := 100
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "foo.1", "Hello World!")
}
nc.Flush()
state := mset.state()
if state.Msgs != uint64(toSend) {
t.Fatalf("Expected %d messages, got %d", toSend, state.Msgs)
}
o, err := mset.addConsumer(&ConsumerConfig{Durable: "PBO", AckPolicy: AckExplicit})
require_NoError(t, err)
defer o.delete()
expectPending := func(ep int) {
t.Helper()
// Now check consumer info.
checkFor(t, time.Second, 10*time.Millisecond, func() error {
if info, pep := o.info(), ep+1; int(info.NumPending) != pep {
return fmt.Errorf("Expected consumer info pending of %d, got %d", pep, info.NumPending)
}
return nil
})
m, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, _, _, _, pending := ackReplyInfo(m.Reply)
if pending != uint64(ep) {
t.Fatalf("Expected ack reply pending of %d, got %d - reply: %q", ep, pending, m.Reply)
}
}
expectPending(toSend - 1)
// Send some more while we are connected.
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "foo.1", "Hello World!")
}
nc.Flush()
expectPending(toSend*2 - 2)
// Purge and send a new one.
mset.purge(nil)
nc.Flush()
sendStreamMsg(t, nc, "foo.1", "Hello World!")
expectPending(0)
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "foo.22", "Hello World!")
}
expectPending(toSend - 1) // 201
// Test that delete will not register for consumed messages.
mset.removeMsg(mset.state().FirstSeq)
expectPending(toSend - 2) // 202
// Now remove one that has not been delivered.
mset.removeMsg(250)
expectPending(toSend - 4) // 203
// Test Expiration.
mset.purge(nil)
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "foo.1", "Hello World!")
}
nc.Flush()
// Wait for expiration to kick in.
checkFor(t, 5*time.Second, time.Second, func() error {
if state := mset.state(); state.Msgs != 0 {
return fmt.Errorf("Stream still has messages")
}
return nil
})
sendStreamMsg(t, nc, "foo.33", "Hello World!")
expectPending(0)
// Now do filtered consumers.
o.delete()
o, err = mset.addConsumer(&ConsumerConfig{Durable: "PBO-FILTERED", AckPolicy: AckExplicit, FilterSubject: "foo.22"})
require_NoError(t, err)
defer o.delete()
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "foo.33", "Hello World!")
}
nc.Flush()
if info := o.info(); info.NumPending != 0 {
t.Fatalf("Expected no pending, got %d", info.NumPending)
}
// Now send one message that will match us.
sendStreamMsg(t, nc, "foo.22", "Hello World!")
expectPending(0)
sendStreamMsg(t, nc, "foo.22", "Hello World!") // 504
sendStreamMsg(t, nc, "foo.22", "Hello World!") // 505
sendStreamMsg(t, nc, "foo.22", "Hello World!") // 506
sendStreamMsg(t, nc, "foo.22", "Hello World!") // 507
expectPending(3)
mset.removeMsg(506)
expectPending(1)
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "foo.22", "Hello World!")
}
nc.Flush()
expectPending(100)
mset.purge(nil)
sendStreamMsg(t, nc, "foo.22", "Hello World!")
expectPending(0)
})
}
}
func TestJetStreamAckReplyStreamPendingWithAcks(t *testing.T) {
msc := StreamConfig{
Name: "MY_STREAM",
Subjects: []string{"foo", "bar", "baz"},
Storage: MemoryStorage,
}
fsc := msc
fsc.Storage = FileStorage
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &msc},
{"FileStore", &fsc},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Now load up some messages.
toSend := 500
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "foo", "Hello Foo!")
sendStreamMsg(t, nc, "bar", "Hello Bar!")
sendStreamMsg(t, nc, "baz", "Hello Baz!")
}
state := mset.state()
if state.Msgs != uint64(toSend*3) {
t.Fatalf("Expected %d messages, got %d", toSend*3, state.Msgs)
}
dsubj := "_d_"
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "D-1",
AckPolicy: AckExplicit,
FilterSubject: "foo",
DeliverSubject: dsubj,
})
require_NoError(t, err)
defer o.delete()
if info := o.info(); int(info.NumPending) != toSend {
t.Fatalf("Expected consumer info pending of %d, got %d", toSend, info.NumPending)
}
sub, _ := nc.SubscribeSync(dsubj)
defer sub.Unsubscribe()
checkFor(t, 500*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != toSend {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, toSend)
}
return nil
})
// Should be zero.
if info := o.info(); int(info.NumPending) != 0 {
t.Fatalf("Expected consumer info pending of %d, got %d", 0, info.NumPending)
} else if info.NumAckPending != toSend {
t.Fatalf("Expected %d to be pending acks, got %d", toSend, info.NumAckPending)
}
})
}
}
func TestJetStreamWorkQueueAckWaitRedelivery(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_WQ", Storage: MemoryStorage, Retention: WorkQueuePolicy}},
{"FileStore", &StreamConfig{Name: "MY_WQ", Storage: FileStorage, Retention: WorkQueuePolicy}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Now load up some messages.
toSend := 100
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, c.mconfig.Name, "Hello World!")
}
state := mset.state()
if state.Msgs != uint64(toSend) {
t.Fatalf("Expected %d messages, got %d", toSend, state.Msgs)
}
ackWait := 100 * time.Millisecond
o, err := mset.addConsumer(&ConsumerConfig{Durable: "PBO", AckPolicy: AckExplicit, AckWait: ackWait})
require_NoError(t, err)
defer o.delete()
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
reqNextMsgSubj := o.requestNextMsgSubject()
// Consume all the messages. But do not ack.
for i := 0; i < toSend; i++ {
nc.PublishRequest(reqNextMsgSubj, sub.Subject, nil)
if _, err := sub.NextMsg(time.Second); err != nil {
t.Fatalf("Unexpected error waiting for messages: %v", err)
}
}
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != 0 {
t.Fatalf("Did not consume all messages, still have %d", nmsgs)
}
// All messages should still be there.
state = mset.state()
if int(state.Msgs) != toSend {
t.Fatalf("Expected %d messages, got %d", toSend, state.Msgs)
}
// Now consume and ack.
for i := 1; i <= toSend; i++ {
nc.PublishRequest(reqNextMsgSubj, sub.Subject, nil)
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Unexpected error waiting for message[%d]: %v", i, err)
}
sseq, dseq, dcount, _, _ := ackReplyInfo(m.Reply)
if sseq != uint64(i) {
t.Fatalf("Expected set sequence of %d , got %d", i, sseq)
}
// Delivery sequences should always increase.
if dseq != uint64(toSend+i) {
t.Fatalf("Expected delivery sequence of %d , got %d", toSend+i, dseq)
}
if dcount == 1 {
t.Fatalf("Expected these to be marked as redelivered")
}
// Ack the message here.
m.AckSync()
}
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != 0 {
t.Fatalf("Did not consume all messages, still have %d", nmsgs)
}
// Flush acks
nc.Flush()
// Now check the mset as well, since we have a WorkQueue retention policy this should be empty.
if state := mset.state(); state.Msgs != 0 {
t.Fatalf("Expected no messages, got %d", state.Msgs)
}
})
}
}
func TestJetStreamWorkQueueNakRedelivery(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_WQ", Storage: MemoryStorage, Retention: WorkQueuePolicy}},
{"FileStore", &StreamConfig{Name: "MY_WQ", Storage: FileStorage, Retention: WorkQueuePolicy}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Now load up some messages.
toSend := 10
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, c.mconfig.Name, "Hello World!")
}
state := mset.state()
if state.Msgs != uint64(toSend) {
t.Fatalf("Expected %d messages, got %d", toSend, state.Msgs)
}
o, err := mset.addConsumer(&ConsumerConfig{Durable: "PBO", AckPolicy: AckExplicit})
require_NoError(t, err)
defer o.delete()
getMsg := func(sseq, dseq int) *nats.Msg {
t.Helper()
m, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
rsseq, rdseq, _, _, _ := ackReplyInfo(m.Reply)
if rdseq != uint64(dseq) {
t.Fatalf("Expected delivered sequence of %d , got %d", dseq, rdseq)
}
if rsseq != uint64(sseq) {
t.Fatalf("Expected store sequence of %d , got %d", sseq, rsseq)
}
return m
}
for i := 1; i <= 5; i++ {
m := getMsg(i, i)
// Ack the message here.
m.Respond(nil)
}
// Grab #6
m := getMsg(6, 6)
// NAK this one and make sure its processed.
m.Respond(AckNak)
nc.Flush()
// When we request again should be store sequence 6 again.
getMsg(6, 7)
// Then we should get 7, 8, etc.
getMsg(7, 8)
getMsg(8, 9)
})
}
}
func TestJetStreamWorkQueueWorkingIndicator(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_WQ", Storage: MemoryStorage, Retention: WorkQueuePolicy}},
{"FileStore", &StreamConfig{Name: "MY_WQ", Storage: FileStorage, Retention: WorkQueuePolicy}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Now load up some messages.
toSend := 2
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, c.mconfig.Name, "Hello World!")
}
state := mset.state()
if state.Msgs != uint64(toSend) {
t.Fatalf("Expected %d messages, got %d", toSend, state.Msgs)
}
ackWait := 100 * time.Millisecond
o, err := mset.addConsumer(&ConsumerConfig{Durable: "PBO", AckPolicy: AckExplicit, AckWait: ackWait})
require_NoError(t, err)
defer o.delete()
getMsg := func(sseq, dseq int) *nats.Msg {
t.Helper()
m, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
rsseq, rdseq, _, _, _ := ackReplyInfo(m.Reply)
if rdseq != uint64(dseq) {
t.Fatalf("Expected delivered sequence of %d , got %d", dseq, rdseq)
}
if rsseq != uint64(sseq) {
t.Fatalf("Expected store sequence of %d , got %d", sseq, rsseq)
}
return m
}
getMsg(1, 1)
// Now wait past ackWait
time.Sleep(ackWait * 2)
// We should get 1 back.
m := getMsg(1, 2)
// Now let's take longer than ackWait to process but signal we are working on the message.
timeout := time.Now().Add(3 * ackWait)
for time.Now().Before(timeout) {
m.Respond(AckProgress)
nc.Flush()
time.Sleep(ackWait / 5)
}
// We should get 2 here, not 1 since we have indicated we are working on it.
m2 := getMsg(2, 3)
time.Sleep(ackWait / 2)
m2.Respond(AckProgress)
// Now should get 1 back then 2.
m = getMsg(1, 4)
m.Respond(nil)
getMsg(2, 5)
})
}
}
func TestJetStreamWorkQueueTerminateDelivery(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "MY_WQ", Storage: MemoryStorage, Retention: WorkQueuePolicy}},
{"FileStore", &StreamConfig{Name: "MY_WQ", Storage: FileStorage, Retention: WorkQueuePolicy}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Now load up some messages.
toSend := 22
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, c.mconfig.Name, "Hello World!")
}
state := mset.state()
if state.Msgs != uint64(toSend) {
t.Fatalf("Expected %d messages, got %d", toSend, state.Msgs)
}
ackWait := 25 * time.Millisecond
o, err := mset.addConsumer(&ConsumerConfig{Durable: "PBO", AckPolicy: AckExplicit, AckWait: ackWait})
require_NoError(t, err)
defer o.delete()
getMsg := func(sseq, dseq int) *nats.Msg {
t.Helper()
m, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
rsseq, rdseq, _, _, _ := ackReplyInfo(m.Reply)
if rdseq != uint64(dseq) {
t.Fatalf("Expected delivered sequence of %d , got %d", dseq, rdseq)
}
if rsseq != uint64(sseq) {
t.Fatalf("Expected store sequence of %d , got %d", sseq, rsseq)
}
return m
}
// Make sure we get the correct advisory
sub, _ := nc.SubscribeSync(JSAdvisoryConsumerMsgTerminatedPre + ".>")
defer sub.Unsubscribe()
getMsg(1, 1)
// Now wait past ackWait
time.Sleep(ackWait * 2)
// We should get 1 back.
m := getMsg(1, 2)
// Now terminate
m.Respond([]byte(fmt.Sprintf("%s with reason", string(AckTerm))))
time.Sleep(ackWait * 2)
// We should get 2 here, not 1 since we have indicated we wanted to terminate.
getMsg(2, 3)
// Check advisory was delivered.
am, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var adv JSConsumerDeliveryTerminatedAdvisory
json.Unmarshal(am.Data, &adv)
if adv.Stream != "MY_WQ" {
t.Fatalf("Expected stream of %s, got %s", "MY_WQ", adv.Stream)
}
if adv.Consumer != "PBO" {
t.Fatalf("Expected consumer of %s, got %s", "PBO", adv.Consumer)
}
if adv.StreamSeq != 1 {
t.Fatalf("Expected stream sequence of %d, got %d", 1, adv.StreamSeq)
}
if adv.ConsumerSeq != 2 {
t.Fatalf("Expected consumer sequence of %d, got %d", 2, adv.ConsumerSeq)
}
if adv.Deliveries != 2 {
t.Fatalf("Expected delivery count of %d, got %d", 2, adv.Deliveries)
}
if adv.Reason != "with reason" {
t.Fatalf("Advisory did not have a reason")
}
})
}
}
func TestJetStreamAckNext(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mname := "ACKNXT"
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: mname, Storage: MemoryStorage})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
o, err := mset.addConsumer(&ConsumerConfig{Durable: "worker", AckPolicy: AckExplicit})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
for i := 0; i < 12; i++ {
sendStreamMsg(t, nc, mname, fmt.Sprintf("msg %d", i))
}
q := make(chan *nats.Msg, 10)
sub, err := nc.ChanSubscribe(nats.NewInbox(), q)
if err != nil {
t.Fatalf("SubscribeSync failed: %s", err)
}
nc.PublishRequest(o.requestNextMsgSubject(), sub.Subject, []byte("1"))
// normal next should imply 1
msg := <-q
err = msg.RespondMsg(&nats.Msg{Reply: sub.Subject, Subject: msg.Reply, Data: AckNext})
if err != nil {
t.Fatalf("RespondMsg failed: %s", err)
}
// read 1 message and check ack was done etc
msg = <-q
if len(q) != 0 {
t.Fatalf("Expected empty q got %d", len(q))
}
if o.info().AckFloor.Stream != 1 {
t.Fatalf("First message was not acknowledged")
}
if !bytes.Equal(msg.Data, []byte("msg 1")) {
t.Fatalf("wrong message received, expected: msg 1 got %q", msg.Data)
}
// now ack and request 5 more using a naked number
err = msg.RespondMsg(&nats.Msg{Reply: sub.Subject, Subject: msg.Reply, Data: append(AckNext, []byte(" 5")...)})
if err != nil {
t.Fatalf("RespondMsg failed: %s", err)
}
getMsgs := func(start, count int) {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
for i := start; i < count+1; i++ {
select {
case msg := <-q:
expect := fmt.Sprintf("msg %d", i+1)
if !bytes.Equal(msg.Data, []byte(expect)) {
t.Fatalf("wrong message received, expected: %s got %#v", expect, msg)
}
case <-ctx.Done():
t.Fatalf("did not receive all messages")
}
}
}
getMsgs(1, 5)
// now ack and request 5 more using the full request
err = msg.RespondMsg(&nats.Msg{Reply: sub.Subject, Subject: msg.Reply, Data: append(AckNext, []byte(`{"batch": 5}`)...)})
if err != nil {
t.Fatalf("RespondMsg failed: %s", err)
}
getMsgs(6, 10)
if o.info().AckFloor.Stream != 2 {
t.Fatalf("second message was not acknowledged")
}
}
func TestJetStreamPublishDeDupe(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mname := "DeDupe"
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: mname, Storage: FileStorage, MaxAge: time.Hour, Subjects: []string{"foo.*"}})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
// Check Duplicates setting.
duplicates := mset.config().Duplicates
if duplicates != StreamDefaultDuplicatesWindow {
t.Fatalf("Expected a default of %v, got %v", StreamDefaultDuplicatesWindow, duplicates)
}
cfg := mset.config()
// Make sure can't be negative.
cfg.Duplicates = -25 * time.Millisecond
if err := mset.update(&cfg); err == nil {
t.Fatalf("Expected an error but got none")
}
// Make sure can't be longer than age if its set.
cfg.Duplicates = 2 * time.Hour
if err := mset.update(&cfg); err == nil {
t.Fatalf("Expected an error but got none")
}
nc := clientConnectToServer(t, s)
defer nc.Close()
sendMsg := func(seq uint64, id, msg string) *PubAck {
t.Helper()
m := nats.NewMsg(fmt.Sprintf("foo.%d", seq))
m.Header.Add(JSMsgId, id)
m.Data = []byte(msg)
resp, _ := nc.RequestMsg(m, 100*time.Millisecond)
if resp == nil {
t.Fatalf("No response for %q, possible timeout?", msg)
}
pa := getPubAckResponse(resp.Data)
if pa == nil || pa.Error != nil {
t.Fatalf("Expected a JetStreamPubAck, got %q", resp.Data)
}
if pa.Sequence != seq {
t.Fatalf("Did not get correct sequence in PubAck, expected %d, got %d", seq, pa.Sequence)
}
return pa.PubAck
}
expect := func(n uint64) {
t.Helper()
state := mset.state()
if state.Msgs != n {
t.Fatalf("Expected %d messages, got %d", n, state.Msgs)
}
}
sendMsg(1, "AA", "Hello DeDupe!")
sendMsg(2, "BB", "Hello DeDupe!")
sendMsg(3, "CC", "Hello DeDupe!")
sendMsg(4, "ZZ", "Hello DeDupe!")
expect(4)
sendMsg(1, "AA", "Hello DeDupe!")
sendMsg(2, "BB", "Hello DeDupe!")
sendMsg(4, "ZZ", "Hello DeDupe!")
expect(4)
cfg = mset.config()
cfg.Duplicates = 100 * time.Millisecond
if err := mset.update(&cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
nmids := func(expected int) {
t.Helper()
checkFor(t, 200*time.Millisecond, 10*time.Millisecond, func() error {
if nids := mset.numMsgIds(); nids != expected {
return fmt.Errorf("Expected %d message ids, got %d", expected, nids)
}
return nil
})
}
nmids(4)
time.Sleep(cfg.Duplicates * 2)
sendMsg(5, "AAA", "Hello DeDupe!")
sendMsg(6, "BBB", "Hello DeDupe!")
sendMsg(7, "CCC", "Hello DeDupe!")
sendMsg(8, "DDD", "Hello DeDupe!")
sendMsg(9, "ZZZ", "Hello DeDupe!")
nmids(5)
// Eventually will drop to zero.
nmids(0)
// Now test server restart
cfg.Duplicates = 30 * time.Minute
if err := mset.update(&cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
mset.purge(nil)
// Send 5 new messages.
sendMsg(10, "AAAA", "Hello DeDupe!")
sendMsg(11, "BBBB", "Hello DeDupe!")
sendMsg(12, "CCCC", "Hello DeDupe!")
sendMsg(13, "DDDD", "Hello DeDupe!")
sendMsg(14, "EEEE", "Hello DeDupe!")
// Stop current
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
// Restart.
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc = clientConnectToServer(t, s)
defer nc.Close()
mset, _ = s.GlobalAccount().lookupStream(mname)
if nms := mset.state().Msgs; nms != 5 {
t.Fatalf("Expected 5 restored messages, got %d", nms)
}
nmids(5)
// Send same and make sure duplicate detection still works.
// Send 5 duplicate messages.
sendMsg(10, "AAAA", "Hello DeDupe!")
sendMsg(11, "BBBB", "Hello DeDupe!")
sendMsg(12, "CCCC", "Hello DeDupe!")
sendMsg(13, "DDDD", "Hello DeDupe!")
sendMsg(14, "EEEE", "Hello DeDupe!")
if nms := mset.state().Msgs; nms != 5 {
t.Fatalf("Expected 5 restored messages, got %d", nms)
}
nmids(5)
// Check we set duplicate properly.
pa := sendMsg(10, "AAAA", "Hello DeDupe!")
if !pa.Duplicate {
t.Fatalf("Expected duplicate to be set")
}
// Purge should NOT wipe the msgIds. They should still persist.
mset.purge(nil)
nmids(5)
}
func getPubAckResponse(msg []byte) *JSPubAckResponse {
var par JSPubAckResponse
if err := json.Unmarshal(msg, &par); err != nil {
return nil
}
return &par
}
func TestJetStreamPublishExpect(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mname := "EXPECT"
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: mname, Storage: FileStorage, MaxAge: time.Hour, Subjects: []string{"foo.*"}})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Test that we get no error when expected stream is correct.
m := nats.NewMsg("foo.bar")
m.Data = []byte("HELLO")
m.Header.Set(JSExpectedStream, mname)
resp, err := nc.RequestMsg(m, 100*time.Millisecond)
require_NoError(t, err)
if pa := getPubAckResponse(resp.Data); pa == nil || pa.Error != nil {
t.Fatalf("Expected a valid JetStreamPubAck, got %q", resp.Data)
}
// Now test that we get an error back when expecting a different stream.
m.Header.Set(JSExpectedStream, "ORDERS")
resp, err = nc.RequestMsg(m, 100*time.Millisecond)
require_NoError(t, err)
if pa := getPubAckResponse(resp.Data); pa == nil || pa.Error == nil {
t.Fatalf("Expected an error, got %q", resp.Data)
}
// Now test that we get an error back when expecting a different sequence number.
m.Header.Set(JSExpectedStream, mname)
m.Header.Set(JSExpectedLastSeq, "10")
resp, err = nc.RequestMsg(m, 100*time.Millisecond)
require_NoError(t, err)
if pa := getPubAckResponse(resp.Data); pa == nil || pa.Error == nil {
t.Fatalf("Expected an error, got %q", resp.Data)
}
// Or if we expect that there are no messages by setting "0" as the expected last seq
m.Header.Set(JSExpectedLastSeq, "0")
resp, err = nc.RequestMsg(m, 100*time.Millisecond)
require_NoError(t, err)
if pa := getPubAckResponse(resp.Data); pa == nil || pa.Error == nil {
t.Fatalf("Expected an error, got %q", resp.Data)
}
// Now send a message with a message ID and make sure we can match that.
m = nats.NewMsg("foo.bar")
m.Data = []byte("HELLO")
m.Header.Set(JSMsgId, "AAA")
if _, err = nc.RequestMsg(m, 100*time.Millisecond); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Now try again with new message ID but require last one to be 'BBB'
m.Header.Set(JSMsgId, "ZZZ")
m.Header.Set(JSExpectedLastMsgId, "BBB")
resp, err = nc.RequestMsg(m, 100*time.Millisecond)
require_NoError(t, err)
if pa := getPubAckResponse(resp.Data); pa == nil || pa.Error == nil {
t.Fatalf("Expected an error, got %q", resp.Data)
}
// Restart the server and make sure we remember/rebuild last seq and last msgId.
// Stop current
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
// Restart.
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc = clientConnectToServer(t, s)
defer nc.Close()
// Our last sequence was 2 and last msgId was "AAA"
m = nats.NewMsg("foo.baz")
m.Data = []byte("HELLO AGAIN")
m.Header.Set(JSExpectedLastSeq, "2")
m.Header.Set(JSExpectedLastMsgId, "AAA")
m.Header.Set(JSMsgId, "BBB")
resp, err = nc.RequestMsg(m, 100*time.Millisecond)
require_NoError(t, err)
if pa := getPubAckResponse(resp.Data); pa == nil || pa.Error != nil {
t.Fatalf("Expected a valid JetStreamPubAck, got %q", resp.Data)
}
}
func TestJetStreamRedeliveryAfterServerRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
sendSubj := "MYQ"
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: sendSubj, Storage: FileStorage})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Now load up some messages.
toSend := 25
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, sendSubj, "Hello World!")
}
state := mset.state()
if state.Msgs != uint64(toSend) {
t.Fatalf("Expected %d messages, got %d", toSend, state.Msgs)
}
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc.Flush()
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "TO",
DeliverSubject: sub.Subject,
AckPolicy: AckExplicit,
AckWait: 100 * time.Millisecond,
})
require_NoError(t, err)
defer o.delete()
checkFor(t, 250*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != toSend {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, toSend)
}
return nil
})
// Capture port since it was dynamic.
u, _ := url.Parse(s.ClientURL())
port, _ := strconv.Atoi(u.Port())
sd := s.JetStreamConfig().StoreDir
// Stop current
s.Shutdown()
// Restart.
s = RunJetStreamServerOnPort(port, sd)
defer s.Shutdown()
// Don't wait for reconnect from old client.
nc = clientConnectToServer(t, s)
defer nc.Close()
sub, _ = nc.SubscribeSync(sub.Subject)
defer sub.Unsubscribe()
checkFor(t, time.Second, 50*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != toSend {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, toSend)
}
return nil
})
}
func TestJetStreamUsageNoReservation(t *testing.T) {
test := func(t *testing.T, replicas int) {
var s *Server
if replicas == 1 {
s = RunBasicJetStreamServer(t)
defer s.Shutdown()
} else {
c := createJetStreamClusterExplicit(t, "R3S", replicas)
defer c.shutdown()
s = c.randomServer()
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "FILE", Storage: nats.FileStorage, Replicas: replicas})
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{Name: "MEM", Storage: nats.MemoryStorage, Replicas: replicas})
require_NoError(t, err)
acc := s.globalAccount()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
if streams := acc.numStreams(); streams != 2 {
return fmt.Errorf("not at 2 streams yet, got %d", streams)
}
return nil
})
stats := acc.JetStreamUsage()
require_Equal(t, stats.ReservedStore, 0)
require_Equal(t, stats.ReservedMemory, 0)
}
t.Run("R1", func(t *testing.T) { test(t, 1) })
t.Run("R3", func(t *testing.T) { test(t, 3) })
}
func TestJetStreamUsageReservationNegativeMaxBytes(t *testing.T) {
test := func(t *testing.T, replicas int) {
var s *Server
if replicas == 1 {
s = RunBasicJetStreamServer(t)
defer s.Shutdown()
} else {
c := createJetStreamClusterExplicit(t, "R3S", replicas)
defer c.shutdown()
s = c.randomServer()
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
fileCfg := &nats.StreamConfig{Name: "FILE", Storage: nats.FileStorage, Replicas: replicas, MaxBytes: 1024}
memCfg := &nats.StreamConfig{Name: "MEM", Storage: nats.MemoryStorage, Replicas: replicas, MaxBytes: 1024}
_, err := js.AddStream(fileCfg)
require_NoError(t, err)
_, err = js.AddStream(memCfg)
require_NoError(t, err)
acc := s.globalAccount()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
if streams := acc.numStreams(); streams != 2 {
return fmt.Errorf("not at 2 streams yet, got %d", streams)
}
return nil
})
validateReserved := func(total uint64) {
t.Helper()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
stats := acc.JetStreamUsage()
if stats.ReservedMemory != total {
return fmt.Errorf("acc stats: expected %d, got %d", total, stats.ReservedMemory)
}
if stats.ReservedStore != total {
return fmt.Errorf("acc stats: expected %d, got %d", total, stats.ReservedStore)
}
jstats := s.getJetStream().usageStats()
if jstats.ReservedMemory != total {
return fmt.Errorf("server stats: expected %d, got %d", total, jstats.ReservedMemory)
}
if jstats.ReservedStore != total {
return fmt.Errorf("server stats: expected %d, got %d", total, jstats.ReservedStore)
}
return nil
})
}
validateReserved(1024)
// Resetting back to zero.
fileCfg.MaxBytes, memCfg.MaxBytes = 0, 0
_, err = js.UpdateStream(fileCfg)
require_NoError(t, err)
_, err = js.UpdateStream(memCfg)
require_NoError(t, err)
validateReserved(0)
// Update to reset back again.
fileCfg.MaxBytes, memCfg.MaxBytes = 512, 512
_, err = js.UpdateStream(fileCfg)
require_NoError(t, err)
_, err = js.UpdateStream(memCfg)
require_NoError(t, err)
validateReserved(512)
// Resetting back to -1 should mean zero as well, and should do proper accounting.
fileCfg.MaxBytes, memCfg.MaxBytes = -1, -1
_, err = js.UpdateStream(fileCfg)
require_NoError(t, err)
_, err = js.UpdateStream(memCfg)
require_NoError(t, err)
validateReserved(0)
}
t.Run("R1", func(t *testing.T) { test(t, 1) })
t.Run("R3", func(t *testing.T) { test(t, 3) })
}
func TestJetStreamCheckBytesLimitsOverflow(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
js := s.getJetStream()
limits := &JetStreamAccountLimits{
MaxStore: 1024,
MaxMemory: 1024,
}
// addBytes + maxBytesOffset overflow.
// In the snapshot-restore path, maxBytesOffset (bc) accumulates from
// hdr.Size and can be non-zero when addBytes is near MaxInt64.
// Previously this silently wrapped negative, bypassing the limit.
js.mu.RLock()
err := js.checkBytesLimits(limits, _EMPTY_, math.MaxInt64, 1, FileStorage, false, 0, 1)
js.mu.RUnlock()
require_Error(t, err)
js.mu.RLock()
err = js.checkBytesLimits(limits, _EMPTY_, math.MaxInt64, 1, MemoryStorage, false, 0, 1)
js.mu.RUnlock()
require_Error(t, err)
// Server-level storeReserved + totalBytes overflow.
origStore := js.storeReserved
js.mu.Lock()
js.storeReserved = math.MaxInt64 - 1
js.mu.Unlock()
js.mu.RLock()
err = js.checkBytesLimits(limits, _EMPTY_, 2, 1, FileStorage, true, 0, 0)
js.mu.RUnlock()
require_Error(t, err)
js.mu.Lock()
js.storeReserved = origStore
js.mu.Unlock()
// Server-level memReserved + totalBytes overflow.
origMem := js.memReserved
js.mu.Lock()
js.memReserved = math.MaxInt64 - 1
js.mu.Unlock()
js.mu.RLock()
err = js.checkBytesLimits(limits, _EMPTY_, 2, 1, MemoryStorage, true, 0, 0)
js.mu.RUnlock()
require_Error(t, err)
js.mu.Lock()
js.memReserved = origMem
js.mu.Unlock()
}
func TestJetStreamAccountReservation(t *testing.T) {
const maxInt64 = int64(math.MaxInt64)
for _, test := range []struct {
name string
tier string
replicas int
bytes int64
expected int64
}{
// Non-positive bytes never reserve anything, regardless of tier/replicas.
{"zero bytes", _EMPTY_, 3, 0, 0},
{"negative bytes untiered", _EMPTY_, 3, -100, 0},
{"negative bytes tiered", "R3", 3, -1, 0},
// Un-tiered (flat) account limit: an R>1 stream counts as Replicas*bytes.
{"untiered R1", _EMPTY_, 1, 100, 100},
{"untiered R0 treated as single", _EMPTY_, 0, 100, 100},
{"untiered R3", _EMPTY_, 3, 100, 300},
{"untiered R5", _EMPTY_, 5, 100, 500},
// Tiered account limit: replication is baked into the tier, counted as-is.
{"tiered R1", "R1", 1, 100, 100},
{"tiered R3", "R3", 3, 100, 100},
// Replicas*bytes saturates at MaxInt64 instead of overflowing.
{"untiered R3 saturates", _EMPTY_, 3, maxInt64, maxInt64},
{"untiered R2 saturates", _EMPTY_, 2, maxInt64/2 + 1, maxInt64},
// Tiered does not multiply, so large bytes pass through untouched.
{"tiered large no multiply", "R3", 3, maxInt64, maxInt64},
} {
t.Run(test.name, func(t *testing.T) {
if got := accountReservation(test.tier, test.replicas, test.bytes); got != test.expected {
t.Fatalf("accountReservation(%q, %d, %d) = %d, want %d",
test.tier, test.replicas, test.bytes, got, test.expected)
}
})
}
}
func TestJetStreamSnapshots(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mname := "MY-STREAM"
subjects := []string{"foo", "bar", "baz"}
cfg := StreamConfig{
Name: mname,
Storage: FileStorage,
Subjects: subjects,
MaxMsgs: 1000,
}
acc := s.GlobalAccount()
mset, err := acc.addStream(&cfg)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
nc := clientConnectToServer(t, s)
defer nc.Close()
// Make sure we send some as floor.
toSend := rand.Intn(200) + 22
for i := 1; i <= toSend; i++ {
msg := fmt.Sprintf("Hello World %d", i)
subj := subjects[rand.Intn(len(subjects))]
sendStreamMsg(t, nc, subj, msg)
}
// Create up to 10 consumers.
numConsumers := rand.Intn(10) + 1
var obs []obsi
for i := 1; i <= numConsumers; i++ {
cname := fmt.Sprintf("WQ-%d", i)
o, err := mset.addConsumer(workerModeConfig(cname))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Now grab some messages.
toReceive := rand.Intn(toSend/2) + 1
for r := 0; r < toReceive; r++ {
resp, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if resp != nil {
resp.Respond(nil)
}
}
obs = append(obs, obsi{o.config(), toReceive})
}
nc.Flush()
// Snapshot state of the stream and consumers.
info := info{mset.config(), mset.state(), obs}
sr, err := mset.snapshot(5*time.Second, false, true)
if err != nil {
t.Fatalf("Error getting snapshot: %v", err)
}
zr := sr.Reader
snapshot, err := io.ReadAll(zr)
if err != nil {
t.Fatalf("Error reading snapshot")
}
// Try to restore from snapshot with current stream present, should error.
r := bytes.NewReader(snapshot)
if _, err := acc.RestoreStream(&info.cfg, r); err == nil {
t.Fatalf("Expected an error trying to restore existing stream")
} else if !strings.Contains(err.Error(), "name already in use") {
t.Fatalf("Incorrect error received: %v", err)
}
// Now delete so we can restore.
pusage := acc.JetStreamUsage()
mset.delete()
r.Reset(snapshot)
mset, err = acc.RestoreStream(&info.cfg, r)
require_NoError(t, err)
// Now compare to make sure they are equal.
if nusage := acc.JetStreamUsage(); !reflect.DeepEqual(nusage, pusage) {
t.Fatalf("Usage does not match after restore: %+v vs %+v", nusage, pusage)
}
if state := mset.state(); !reflect.DeepEqual(state, info.state) {
t.Fatalf("State does not match: %+v vs %+v", state, info.state)
}
if cfg := mset.config(); !reflect.DeepEqual(cfg, info.cfg) {
t.Fatalf("Configs do not match: %+v vs %+v", cfg, info.cfg)
}
// Consumers.
if mset.numConsumers() != len(info.obs) {
t.Fatalf("Number of consumers do not match: %d vs %d", mset.numConsumers(), len(info.obs))
}
for _, oi := range info.obs {
if o := mset.lookupConsumer(oi.cfg.Durable); o != nil {
if uint64(oi.ack+1) != o.nextSeq() {
t.Fatalf("[%v] Consumer next seq is not correct: %d vs %d", o.String(), oi.ack+1, o.nextSeq())
}
} else {
t.Fatalf("Expected to get an consumer")
}
}
// Now try restoring to a different
s2 := RunBasicJetStreamServer(t)
defer s2.Shutdown()
acc = s2.GlobalAccount()
r.Reset(snapshot)
mset, err = acc.RestoreStream(&info.cfg, r)
require_NoError(t, err)
o := mset.lookupConsumer("WQ-1")
if o == nil {
t.Fatalf("Could not lookup consumer")
}
nc2 := clientConnectToServer(t, s2)
defer nc2.Close()
// Make sure we can read messages.
if _, err := nc2.Request(o.requestNextMsgSubject(), nil, 5*time.Second); err != nil {
t.Fatalf("Unexpected error getting next message: %v", err)
}
}
func TestJetStreamSnapshotsAPI(t *testing.T) {
lopts := DefaultTestOptions
lopts.ServerName = "LS"
lopts.Port = -1
lopts.LeafNode.Host = lopts.Host
lopts.LeafNode.Port = -1
ls := RunServer(&lopts)
defer ls.Shutdown()
opts := DefaultTestOptions
opts.ServerName = "S"
opts.Port = -1
tdir := t.TempDir()
opts.JetStream = true
opts.JetStreamDomain = "domain"
opts.StoreDir = tdir
maxStore := int64(1024 * 1024 * 1024)
opts.maxStoreSet = true
opts.JetStreamMaxStore = maxStore
rurl, _ := url.Parse(fmt.Sprintf("nats-leaf://%s:%d", lopts.LeafNode.Host, lopts.LeafNode.Port))
opts.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{rurl}}}
s := RunServer(&opts)
defer s.Shutdown()
checkLeafNodeConnected(t, s)
mname := "MY-STREAM"
subjects := []string{"foo", "bar", "baz"}
cfg := StreamConfig{
Name: mname,
Storage: FileStorage,
Subjects: subjects,
MaxMsgs: 1000,
}
acc := s.GlobalAccount()
mset, err := acc.addStreamWithStore(&cfg, &FileStoreConfig{BlockSize: 128})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
nc := clientConnectToServer(t, s)
defer nc.Close()
toSend := rand.Intn(100) + 1
for i := 1; i <= toSend; i++ {
msg := fmt.Sprintf("Hello World %d", i)
subj := subjects[rand.Intn(len(subjects))]
sendStreamMsg(t, nc, subj, msg)
}
o, err := mset.addConsumer(workerModeConfig("WQ"))
require_NoError(t, err)
// Now grab some messages.
toReceive := rand.Intn(toSend) + 1
for r := 0; r < toReceive; r++ {
resp, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if resp != nil {
resp.Respond(nil)
}
}
// Make sure we get proper error for non-existent request, streams,etc,
rmsg, err := nc.Request(fmt.Sprintf(JSApiStreamSnapshotT, "foo"), nil, 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 || resp.Error.Code != 400 || resp.Error.Description != "bad request" {
t.Fatalf("Did not get correct error response: %+v", resp.Error)
}
sreq := &JSApiStreamSnapshotRequest{}
req, _ := json.Marshal(sreq)
rmsg, err = nc.Request(fmt.Sprintf(JSApiStreamSnapshotT, "foo"), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error on snapshot request: %v", err)
}
json.Unmarshal(rmsg.Data, &resp)
if resp.Error == nil || resp.Error.Code != 404 || resp.Error.Description != "stream not found" {
t.Fatalf("Did not get correct error response: %+v", resp.Error)
}
rmsg, err = nc.Request(fmt.Sprintf(JSApiStreamSnapshotT, mname), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error on snapshot request: %v", err)
}
json.Unmarshal(rmsg.Data, &resp)
if resp.Error == nil || resp.Error.Code != 400 || resp.Error.Description != "deliver subject not valid" {
t.Fatalf("Did not get correct error response: %+v", resp.Error)
}
// Set delivery subject, do not subscribe yet. Want this to be an ok pattern.
sreq.DeliverSubject = nats.NewInbox()
// Just for test, usually left alone.
sreq.ChunkSize = 1024
req, _ = json.Marshal(sreq)
rmsg, err = nc.Request(fmt.Sprintf(JSApiStreamSnapshotT, mname), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error on snapshot request: %v", err)
}
resp.Error = nil
json.Unmarshal(rmsg.Data, &resp)
if resp.Error != nil {
t.Fatalf("Did not get correct error response: %+v", resp.Error)
}
// Check that we have the config and the state.
if resp.Config == nil {
t.Fatalf("Expected a stream config in the response, got %+v\n", resp)
}
if resp.State == nil {
t.Fatalf("Expected a stream state in the response, got %+v\n", resp)
}
// Grab state for comparison.
state := *resp.State
config := *resp.Config
// Setup to process snapshot chunks.
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")
}
// Now make sure this snapshot is legit.
var rresp JSApiStreamRestoreResponse
rreq := &JSApiStreamRestoreRequest{
Config: config,
State: state,
}
req, _ = json.Marshal(rreq)
// Make sure we get an error since stream still exists.
rmsg, err = nc.Request(fmt.Sprintf(JSApiStreamRestoreT, mname), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error on snapshot request: %v", err)
}
json.Unmarshal(rmsg.Data, &rresp)
if !IsNatsErr(rresp.Error, JSStreamNameExistRestoreFailedErr) {
t.Fatalf("Did not get correct error response: %+v", rresp.Error)
}
// Delete this stream.
mset.delete()
// Sending no request message will error now.
rmsg, err = nc.Request(fmt.Sprintf(JSApiStreamRestoreT, mname), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error on snapshot request: %v", err)
}
// Make sure to clear.
rresp.Error = nil
json.Unmarshal(rmsg.Data, &rresp)
if rresp.Error == nil || rresp.Error.Code != 400 || rresp.Error.Description != "bad request" {
t.Fatalf("Did not get correct error response: %+v", rresp.Error)
}
// This should work.
rmsg, err = nc.Request(fmt.Sprintf(JSApiStreamRestoreT, mname), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error on snapshot request: %v", err)
}
// Make sure to clear.
rresp.Error = nil
json.Unmarshal(rmsg.Data, &rresp)
if rresp.Error != nil {
t.Fatalf("Got an unexpected error response: %+v", rresp.Error)
}
// Can be any size message.
var chunk [512]byte
for r := bytes.NewReader(snapshot); ; {
n, err := r.Read(chunk[:])
if err != nil {
break
}
nc.Request(rresp.DeliverSubject, chunk[:n], time.Second)
}
nc.Request(rresp.DeliverSubject, nil, time.Second)
mset, err = acc.lookupStream(mname)
if err != nil {
t.Fatalf("Expected to find a stream for %q", mname)
}
if !reflect.DeepEqual(mset.state(), state) {
t.Fatalf("Did not match states, %+v vs %+v", mset.state(), state)
}
// Now ask that the stream be checked first.
sreq.ChunkSize = 0
sreq.CheckMsgs = true
snapshot = snapshot[:0]
req, _ = json.Marshal(sreq)
if _, err = nc.Request(fmt.Sprintf(JSApiStreamSnapshotT, mname), req, 5*time.Second); err != nil {
t.Fatalf("Unexpected error on snapshot request: %v", err)
}
// Wait to receive the snapshot.
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive our snapshot in time")
}
// Now connect through a cluster server and make sure we can get things to work this way as well.
// This client, connecting to a leaf without shared system account and domain needs to provide the domain explicitly.
nc2 := clientConnectToServer(t, ls)
defer nc2.Close()
// Wait a bit for interest to propagate.
time.Sleep(100 * time.Millisecond)
snapshot = snapshot[:0]
req, _ = json.Marshal(sreq)
rmsg, err = nc2.Request(fmt.Sprintf(strings.ReplaceAll(JSApiStreamSnapshotT, JSApiPrefix, "$JS.domain.API"), mname), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error on snapshot request: %v", err)
}
resp.Error = nil
json.Unmarshal(rmsg.Data, &resp)
if resp.Error != nil {
t.Fatalf("Did not get correct error response: %+v", resp.Error)
}
// Wait to receive the snapshot.
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive our snapshot in time")
}
// Now do a restore through the new client connection.
// Delete this stream first.
mset, err = acc.lookupStream(mname)
if err != nil {
t.Fatalf("Expected to find a stream for %q", mname)
}
state = mset.state()
mset.delete()
req, _ = json.Marshal(rreq)
rmsg, err = nc2.Request(fmt.Sprintf(strings.ReplaceAll(JSApiStreamRestoreT, JSApiPrefix, "$JS.domain.API"), mname), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error on snapshot request: %v", err)
}
// Make sure to clear.
rresp.Error = nil
json.Unmarshal(rmsg.Data, &rresp)
if rresp.Error != nil {
t.Fatalf("Got an unexpected error response: %+v", rresp.Error)
}
// Make sure when we send something without a reply subject the subscription is shutoff.
r := bytes.NewReader(snapshot)
n, _ := r.Read(chunk[:])
nc2.Publish(rresp.DeliverSubject, chunk[:n])
nc2.Flush()
n, _ = r.Read(chunk[:])
if _, err := nc2.Request(rresp.DeliverSubject, chunk[:n], 100*time.Millisecond); err == nil {
t.Fatalf("Expected restore subscription to be closed")
}
req, _ = json.Marshal(rreq)
rmsg, err = nc2.Request(fmt.Sprintf(strings.ReplaceAll(JSApiStreamRestoreT, JSApiPrefix, "$JS.domain.API"), mname), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error on snapshot request: %v", err)
}
// Make sure to clear.
rresp.Error = nil
json.Unmarshal(rmsg.Data, &rresp)
if rresp.Error != nil {
t.Fatalf("Got an unexpected error response: %+v", rresp.Error)
}
for r := bytes.NewReader(snapshot); ; {
n, err := r.Read(chunk[:])
if err != nil {
break
}
// Make sure other side responds to reply subjects for ack flow. Optional.
if _, err := nc2.Request(rresp.DeliverSubject, chunk[:n], time.Second); err != nil {
t.Fatalf("Restore not honoring reply subjects for ack flow")
}
}
// For EOF this will send back stream info or an error.
si, err := nc2.Request(rresp.DeliverSubject, nil, time.Second)
if err != nil {
t.Fatalf("Got an error restoring stream: %v", err)
}
var scResp JSApiStreamCreateResponse
if err := json.Unmarshal(si.Data, &scResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if scResp.Error != nil {
t.Fatalf("Got an unexpected error from EOF on restore: %+v", scResp.Error)
}
if !reflect.DeepEqual(scResp.StreamInfo.State, state) {
t.Fatalf("Did not match states, %+v vs %+v", scResp.StreamInfo.State, state)
}
// Now make sure that if we try to change the name/identity of the stream we get an error.
mset, err = acc.lookupStream(mname)
if err != nil {
t.Fatalf("Expected to find a stream for %q", mname)
}
mset.state()
mset.delete()
rreq.Config.Name = "NEW_STREAM"
req, _ = json.Marshal(rreq)
rmsg, err = nc2.Request(fmt.Sprintf(strings.ReplaceAll(JSApiStreamRestoreT, JSApiPrefix, "$JS.domain.API"), rreq.Config.Name), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error on snapshot request: %v", err)
}
// Make sure to clear.
rresp.Error = nil
json.Unmarshal(rmsg.Data, &rresp)
// We should not get an error here.
if rresp.Error != nil {
t.Fatalf("Got an unexpected error response: %+v", rresp.Error)
}
for r := bytes.NewReader(snapshot); ; {
n, err := r.Read(chunk[:])
if err != nil {
break
}
nc2.Request(rresp.DeliverSubject, chunk[:n], time.Second)
}
si, err = nc2.Request(rresp.DeliverSubject, nil, time.Second)
require_NoError(t, err)
scResp.Error = nil
if err := json.Unmarshal(si.Data, &scResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if scResp.Error == nil {
t.Fatalf("Expected an error but got none")
}
expect := "names do not match"
if !strings.Contains(scResp.Error.Description, expect) {
t.Fatalf("Expected description of %q, got %q", expect, scResp.Error.Description)
}
}
func TestJetStreamPubAckPerf(t *testing.T) {
// Comment out to run, holding place for now.
t.SkipNow()
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "foo", Storage: nats.MemoryStorage}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
toSend := 1_000_000
start := time.Now()
for i := 0; i < toSend; i++ {
js.PublishAsync("foo", []byte("OK"))
}
<-js.PublishAsyncComplete()
tt := time.Since(start)
fmt.Printf("time is %v\n", tt)
fmt.Printf("%.0f msgs/sec\n", float64(toSend)/tt.Seconds())
}
func TestJetStreamPubPerfWithFullStream(t *testing.T) {
// Comment out to run, holding place for now.
t.SkipNow()
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
toSend, msg := 1_000_000, []byte("OK")
_, err := js.AddStream(&nats.StreamConfig{Name: "foo", MaxMsgs: int64(toSend)})
require_NoError(t, err)
start := time.Now()
for i := 0; i < toSend; i++ {
js.PublishAsync("foo", msg)
}
<-js.PublishAsyncComplete()
tt := time.Since(start)
fmt.Printf("time is %v\n", tt)
fmt.Printf("%.0f msgs/sec\n", float64(toSend)/tt.Seconds())
// Now do same amount but knowing we are at our limit.
start = time.Now()
for i := 0; i < toSend; i++ {
js.PublishAsync("foo", msg)
}
<-js.PublishAsyncComplete()
tt = time.Since(start)
fmt.Printf("\ntime is %v\n", tt)
fmt.Printf("%.0f msgs/sec\n", float64(toSend)/tt.Seconds())
}
func TestJetStreamSnapshotsAPIPerf(t *testing.T) {
// Comment out to run, holding place for now.
t.SkipNow()
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
cfg := StreamConfig{
Name: "snap-perf",
Storage: FileStorage,
}
acc := s.GlobalAccount()
if _, err := acc.addStream(&cfg); err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
nc := clientConnectToServer(t, s)
defer nc.Close()
msg := make([]byte, 128*1024)
// If you don't give gzip some data will spend too much time compressing everything to zero.
crand.Read(msg)
for i := 0; i < 10000; i++ {
nc.Publish("snap-perf", msg)
}
nc.Flush()
sreq := &JSApiStreamSnapshotRequest{DeliverSubject: nats.NewInbox()}
req, _ := json.Marshal(sreq)
rmsg, err := nc.Request(fmt.Sprintf(JSApiStreamSnapshotT, "snap-perf"), 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)
}
done := make(chan bool)
total := 0
sub, _ := nc.Subscribe(sreq.DeliverSubject, func(m *nats.Msg) {
// EOF
if len(m.Data) == 0 {
m.Sub.Unsubscribe()
done <- true
return
}
// We don't do anything with the snapshot, just take
// note of the size.
total += len(m.Data)
// Flow ack
m.Respond(nil)
})
defer sub.Unsubscribe()
start := time.Now()
// Wait to receive the snapshot.
select {
case <-done:
case <-time.After(30 * time.Second):
t.Fatalf("Did not receive our snapshot in time")
}
td := time.Since(start)
fmt.Printf("Received %d bytes in %v\n", total, td)
fmt.Printf("Rate %.0f MB/s\n", float64(total)/td.Seconds()/(1024*1024))
}
func TestJetStreamActiveDelivery(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "ADS", Storage: MemoryStorage, Subjects: []string{"foo.*"}}},
{"FileStore", &StreamConfig{Name: "ADS", Storage: FileStorage, Subjects: []string{"foo.*"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Now load up some messages.
toSend := 100
sendSubj := "foo.22"
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, sendSubj, "Hello World!")
}
state := mset.state()
if state.Msgs != uint64(toSend) {
t.Fatalf("Expected %d messages, got %d", toSend, state.Msgs)
}
o, err := mset.addConsumer(&ConsumerConfig{Durable: "to", DeliverSubject: "d"})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
// We have no active interest above. So consumer will be considered inactive. Let's subscribe and make sure
// we get the messages instantly. This will test that we hook interest activation correctly.
sub, _ := nc.SubscribeSync("d")
defer sub.Unsubscribe()
nc.Flush()
checkFor(t, 100*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != toSend {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, toSend)
}
return nil
})
})
}
}
func TestJetStreamEphemeralConsumers(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "EP", Storage: MemoryStorage, Subjects: []string{"foo.*"}}},
{"FileStore", &StreamConfig{Name: "EP", Storage: FileStorage, Subjects: []string{"foo.*"}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc.Flush()
o, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: sub.Subject})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !o.isActive() {
t.Fatalf("Expected the consumer to be considered active")
}
if numo := mset.numConsumers(); numo != 1 {
t.Fatalf("Expected number of consumers to be 1, got %d", numo)
}
// Set our delete threshold to something low for testing purposes.
o.setInActiveDeleteThreshold(100 * time.Millisecond)
// Make sure works now.
nc.Request("foo.22", nil, 100*time.Millisecond)
checkFor(t, 250*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != 1 {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, 1)
}
return nil
})
// Now close the subscription, this should trip active state on the ephemeral consumer.
sub.Unsubscribe()
checkFor(t, time.Second, 10*time.Millisecond, func() error {
if o.isActive() {
return fmt.Errorf("Expected the ephemeral consumer to be considered inactive")
}
return nil
})
// The reason for this still being 1 is that we give some time in case of a reconnect scenario.
// We detect right away on the interest change but we wait for interest to be re-established.
// This is in case server goes away but app is fine, we do not want to recycle those consumers.
if numo := mset.numConsumers(); numo != 1 {
t.Fatalf("Expected number of consumers to be 1, got %d", numo)
}
// We should delete this one after the delete threshold.
checkFor(t, time.Second, 100*time.Millisecond, func() error {
if numo := mset.numConsumers(); numo != 0 {
return fmt.Errorf("Expected number of consumers to be 0, got %d", numo)
}
return nil
})
})
}
}
func TestJetStreamMetadata(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DC", Retention: WorkQueuePolicy, Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "DC", Retention: WorkQueuePolicy, Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
for i := 0; i < 10; i++ {
nc.Publish("DC", []byte("OK!"))
nc.Flush()
time.Sleep(time.Millisecond)
}
if state := mset.state(); state.Msgs != 10 {
t.Fatalf("Expected %d messages, got %d", 10, state.Msgs)
}
o, err := mset.addConsumer(workerModeConfig("WQ"))
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
for i := uint64(1); i <= 10; i++ {
m, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sseq, dseq, dcount, ts, _ := ackReplyInfo(m.Reply)
mreq := &JSApiMsgGetRequest{Seq: sseq}
req, err := json.Marshal(mreq)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Load the original message from the stream to verify ReplyInfo ts against stored message
smsgj, err := nc.Request(fmt.Sprintf(JSApiMsgGetT, c.mconfig.Name), req, time.Second)
if err != nil {
t.Fatalf("Could not retrieve stream message: %v", err)
}
var resp JSApiMsgGetResponse
err = json.Unmarshal(smsgj.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")
}
smsg := resp.Message
if ts != smsg.Time.UnixNano() {
t.Fatalf("Wrong timestamp in ReplyInfo for msg %d, expected %v got %v", i, ts, smsg.Time.UnixNano())
}
if sseq != i {
t.Fatalf("Expected set sequence of %d, got %d", i, sseq)
}
if dseq != i {
t.Fatalf("Expected delivery sequence of %d, got %d", i, dseq)
}
if dcount != 1 {
t.Fatalf("Expected delivery count to be 1, got %d", dcount)
}
m.Respond(AckAck)
}
// Now make sure we get right response when message is missing.
mreq := &JSApiMsgGetRequest{Seq: 1}
req, err := json.Marshal(mreq)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Load the original message from the stream to verify ReplyInfo ts against stored message
rmsg, err := nc.Request(fmt.Sprintf(JSApiMsgGetT, c.mconfig.Name), req, time.Second)
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.Error == nil || resp.Error.Code != 404 || resp.Error.Description != "no message found" {
t.Fatalf("Did not get correct error response: %+v", resp.Error)
}
})
}
}
func TestJetStreamRedeliverCount(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DC", Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "DC", Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, js := jsClientConnect(t, s)
defer nc.Close()
if _, err = js.Publish("DC", []byte("OK!")); err != nil {
t.Fatal(err)
}
checkFor(t, time.Second, time.Millisecond*250, func() error {
if state := mset.state(); state.Msgs != 1 {
return fmt.Errorf("Expected %d messages, got %d", 1, state.Msgs)
}
return nil
})
o, err := mset.addConsumer(workerModeConfig("WQ"))
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
for i := uint64(1); i <= 10; i++ {
m, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sseq, dseq, dcount, _, _ := ackReplyInfo(m.Reply)
// Make sure we keep getting stream sequence #1
if sseq != 1 {
t.Fatalf("Expected set sequence of 1, got %d", sseq)
}
if dseq != i {
t.Fatalf("Expected delivery sequence of %d, got %d", i, dseq)
}
// Now make sure dcount is same as dseq (or i).
if dcount != i {
t.Fatalf("Expected delivery count to be %d, got %d", i, dcount)
}
// Make sure it keeps getting sent back.
m.Respond(AckNak)
nc.Flush()
}
})
}
}
// We want to make sure that for pull based consumers that if we ack
// late with no interest the redelivery attempt is removed and we do
// not get the message back.
func TestJetStreamRedeliverAndLateAck(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: "LA", Storage: MemoryStorage})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
o, err := mset.addConsumer(&ConsumerConfig{Durable: "DDD", AckPolicy: AckExplicit, AckWait: 100 * time.Millisecond})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Queue up message
sendStreamMsg(t, nc, "LA", "Hello World!")
nextSubj := o.requestNextMsgSubject()
msg, err := nc.Request(nextSubj, nil, time.Second)
require_NoError(t, err)
// Wait for past ackwait time
time.Sleep(150 * time.Millisecond)
// Now ack!
msg.AckSync()
// We should not get this back.
if _, err := nc.Request(nextSubj, nil, 10*time.Millisecond); err == nil {
t.Fatalf("Message should not have been sent back")
}
}
// https://github.com/nats-io/nats-server/issues/1502
func TestJetStreamPendingNextTimer(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: "NT", Storage: MemoryStorage, Subjects: []string{"ORDERS.*"}})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "DDD",
AckPolicy: AckExplicit,
FilterSubject: "ORDERS.test",
AckWait: 100 * time.Millisecond,
})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
sendAndReceive := func() {
nc := clientConnectToServer(t, s)
defer nc.Close()
// Queue up message
sendStreamMsg(t, nc, "ORDERS.test", "Hello World! #1")
sendStreamMsg(t, nc, "ORDERS.test", "Hello World! #2")
nextSubj := o.requestNextMsgSubject()
for i := 0; i < 2; i++ {
if _, err := nc.Request(nextSubj, nil, time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
nc.Close()
time.Sleep(200 * time.Millisecond)
}
sendAndReceive()
sendAndReceive()
sendAndReceive()
}
func TestJetStreamCanNotNakAckd(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DC", Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "DC", Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Send 10 msgs
for i := 0; i < 10; i++ {
js.Publish("DC", []byte("OK!"))
}
if state := mset.state(); state.Msgs != 10 {
t.Fatalf("Expected %d messages, got %d", 10, state.Msgs)
}
o, err := mset.addConsumer(workerModeConfig("WQ"))
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
for i := uint64(1); i <= 10; i++ {
m, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Ack evens.
if i%2 == 0 {
m.Respond(nil)
}
}
nc.Flush()
// Fake these for now.
ackReplyT := "$JS.A.DC.WQ.1.%d.%d"
checkBadNak := func(seq int) {
t.Helper()
if err := nc.Publish(fmt.Sprintf(ackReplyT, seq, seq), AckNak); err != nil {
t.Fatalf("Error sending nak: %v", err)
}
nc.Flush()
if _, err := nc.Request(o.requestNextMsgSubject(), nil, 10*time.Millisecond); err != nats.ErrTimeout {
t.Fatalf("Did not expect new delivery on nak of %d", seq)
}
}
// If the nak took action it will deliver another message, incrementing the next delivery seq.
// We ack evens above, so these should fail
for i := 2; i <= 10; i += 2 {
checkBadNak(i)
}
// Now check we can not nak something we do not have.
checkBadNak(22)
})
}
}
func TestJetStreamStreamPurge(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DC", Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "DC", Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Send 100 msgs
for i := 0; i < 100; i++ {
js.Publish("DC", []byte("OK!"))
}
if state := mset.state(); state.Msgs != 100 {
t.Fatalf("Expected %d messages, got %d", 100, state.Msgs)
}
mset.purge(nil)
state := mset.state()
if state.Msgs != 0 {
t.Fatalf("Expected %d messages, got %d", 0, state.Msgs)
}
// Make sure first timestamp are reset.
if !state.FirstTime.IsZero() {
t.Fatalf("Expected the state's first time to be zero after purge")
}
time.Sleep(10 * time.Millisecond)
now := time.Now()
js.Publish("DC", []byte("OK!"))
state = mset.state()
if state.Msgs != 1 {
t.Fatalf("Expected %d message, got %d", 1, state.Msgs)
}
if state.FirstTime.Before(now) {
t.Fatalf("First time is incorrect after adding messages back in")
}
if state.FirstTime != state.LastTime {
t.Fatalf("Expected first and last times to be the same for only message")
}
})
}
}
func TestJetStreamStreamPurgeWithConsumer(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DC", Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "DC", Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Send 100 msgs
for i := 0; i < 100; i++ {
js.Publish("DC", []byte("OK!"))
}
if state := mset.state(); state.Msgs != 100 {
t.Fatalf("Expected %d messages, got %d", 100, state.Msgs)
}
// Now create an consumer and make sure it functions properly.
o, err := mset.addConsumer(workerModeConfig("WQ"))
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
nextSubj := o.requestNextMsgSubject()
for i := 0; i < 50; i++ {
msg, err := nc.Request(nextSubj, nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Ack.
msg.Respond(nil)
}
// Now grab next 25 without ack.
for i := 0; i < 25; i++ {
if _, err := nc.Request(nextSubj, nil, time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
state := o.info()
if state.AckFloor.Consumer != 50 {
t.Fatalf("Expected ack floor of 50, got %d", state.AckFloor.Consumer)
}
if state.NumAckPending != 25 {
t.Fatalf("Expected len(pending) to be 25, got %d", state.NumAckPending)
}
// Now do purge.
mset.purge(nil)
if state := mset.state(); state.Msgs != 0 {
t.Fatalf("Expected %d messages, got %d", 0, state.Msgs)
}
// Now re-acquire state and check that we did the right thing.
// Pending should be cleared, and stream sequences should have been set
// to the total messages before purge + 1.
state = o.info()
if state.NumAckPending != 0 {
t.Fatalf("Expected no pending, got %d", state.NumAckPending)
}
if state.Delivered.Stream != 100 {
t.Fatalf("Expected to have setseq now at next seq of 100, got %d", state.Delivered.Stream)
}
// Check AckFloors which should have also been adjusted.
if state.AckFloor.Stream != 100 {
t.Fatalf("Expected ackfloor for setseq to be 100, got %d", state.AckFloor.Stream)
}
if state.AckFloor.Consumer != 75 {
t.Fatalf("Expected ackfloor for obsseq to be 75, got %d", state.AckFloor.Consumer)
}
// Also make sure we can get new messages correctly.
js.Publish("DC", []byte("OK-22"))
if msg, err := nc.Request(nextSubj, nil, time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
} else if string(msg.Data) != "OK-22" {
t.Fatalf("Received wrong message, wanted 'OK-22', got %q", msg.Data)
}
})
}
}
func TestJetStreamStreamPurgeWithConsumerAndRedelivery(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DC", Storage: MemoryStorage}},
{"FileStore", &StreamConfig{Name: "DC", Storage: FileStorage}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Send 100 msgs
for i := 0; i < 100; i++ {
js.Publish("DC", []byte("OK!"))
}
if state := mset.state(); state.Msgs != 100 {
t.Fatalf("Expected %d messages, got %d", 100, state.Msgs)
}
// Now create an consumer and make sure it functions properly.
// This will test redelivery state and purge of the stream.
wcfg := &ConsumerConfig{
Durable: "WQ",
AckPolicy: AckExplicit,
AckWait: 20 * time.Millisecond,
}
o, err := mset.addConsumer(wcfg)
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
nextSubj := o.requestNextMsgSubject()
for i := 0; i < 50; i++ {
// Do not ack these.
if _, err := nc.Request(nextSubj, nil, time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
// Now wait to make sure we are in a redelivered state.
time.Sleep(wcfg.AckWait * 2)
// Now do purge.
mset.purge(nil)
if state := mset.state(); state.Msgs != 0 {
t.Fatalf("Expected %d messages, got %d", 0, state.Msgs)
}
// Now get the state and check that we did the right thing.
// Pending should be cleared, and stream sequences should have been set
// to the total messages before purge + 1.
state := o.info()
if state.NumAckPending != 0 {
t.Fatalf("Expected no pending, got %d", state.NumAckPending)
}
if state.Delivered.Stream != 100 {
t.Fatalf("Expected to have setseq now at next seq of 100, got %d", state.Delivered.Stream)
}
// Check AckFloors which should have also been adjusted.
if state.AckFloor.Stream != 100 {
t.Fatalf("Expected ackfloor for setseq to be 100, got %d", state.AckFloor.Stream)
}
if state.AckFloor.Consumer != 50 {
t.Fatalf("Expected ackfloor for obsseq to be 75, got %d", state.AckFloor.Consumer)
}
// Also make sure we can get new messages correctly.
js.Publish("DC", []byte("OK-22"))
if msg, err := nc.Request(nextSubj, nil, time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
} else if string(msg.Data) != "OK-22" {
t.Fatalf("Received wrong message, wanted 'OK-22', got %q", msg.Data)
}
})
}
}
func TestJetStreamInterestRetentionStream(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DC", Storage: MemoryStorage, Retention: InterestPolicy}},
{"FileStore", &StreamConfig{Name: "DC", Storage: FileStorage, Retention: InterestPolicy}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Send 100 msgs
totalMsgs := 100
for i := 0; i < totalMsgs; i++ {
js.Publish("DC", []byte("OK!"))
}
checkNumMsgs := func(numExpected int) {
t.Helper()
checkFor(t, time.Second, 15*time.Millisecond, func() error {
if state := mset.state(); state.Msgs != uint64(numExpected) {
return fmt.Errorf("Expected %d messages, got %d", numExpected, state.Msgs)
}
return nil
})
}
// Since we had no interest this should be 0.
checkNumMsgs(0)
syncSub := func() *nats.Subscription {
sub, _ := nc.SubscribeSync(nats.NewInbox())
nc.Flush()
return sub
}
// Now create three consumers.
// 1. AckExplicit
// 2. AckAll
// 3. AckNone
sub1 := syncSub()
mset.addConsumer(&ConsumerConfig{DeliverSubject: sub1.Subject, AckPolicy: AckExplicit})
sub2 := syncSub()
mset.addConsumer(&ConsumerConfig{DeliverSubject: sub2.Subject, AckPolicy: AckAll})
sub3 := syncSub()
mset.addConsumer(&ConsumerConfig{DeliverSubject: sub3.Subject, AckPolicy: AckNone})
for i := 0; i < totalMsgs; i++ {
js.Publish("DC", []byte("OK!"))
}
checkNumMsgs(totalMsgs)
// Wait for all messsages to be pending for each sub.
for i, sub := range []*nats.Subscription{sub1, sub2, sub3} {
checkFor(t, 5*time.Second, 25*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); nmsgs != totalMsgs {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d for sub %d", nmsgs, totalMsgs, i+1)
}
return nil
})
}
getAndAck := func(sub *nats.Subscription) {
t.Helper()
if m, err := sub.NextMsg(time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
} else {
m.Respond(nil)
}
nc.Flush()
}
// Ack evens for the explicit ack sub.
var odds []*nats.Msg
for i := 1; i <= totalMsgs; i++ {
if m, err := sub1.NextMsg(time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
} else if i%2 == 0 {
m.Respond(nil) // Ack evens.
} else {
odds = append(odds, m)
}
}
nc.Flush()
checkNumMsgs(totalMsgs)
// Now ack first for AckAll sub2
getAndAck(sub2)
// We should be at the same number since we acked 1, explicit acked 2
checkNumMsgs(totalMsgs)
// Now ack second for AckAll sub2
getAndAck(sub2)
// We should now have 1 removed.
checkNumMsgs(totalMsgs - 1)
// Now ack third for AckAll sub2
getAndAck(sub2)
// We should still only have 1 removed.
checkNumMsgs(totalMsgs - 1)
// Now ack odds from explicit.
for _, m := range odds {
m.Respond(nil) // Ack
}
nc.Flush()
// we should have 1, 2, 3 acks now.
checkNumMsgs(totalMsgs - 3)
nm, _, _ := sub2.Pending()
// Now ack last ackAll message. This should clear all of them.
for i := 1; i <= nm; i++ {
if m, err := sub2.NextMsg(time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
} else if i == nm {
m.Respond(nil)
}
}
nc.Flush()
// Should be zero now.
checkNumMsgs(0)
})
}
}
func TestJetStreamInterestRetentionStreamWithFilteredConsumers(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "DC", Subjects: []string{"*"}, Storage: MemoryStorage, Retention: InterestPolicy}},
{"FileStore", &StreamConfig{Name: "DC", Subjects: []string{"*"}, Storage: FileStorage, Retention: InterestPolicy}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, js := jsClientConnect(t, s)
defer nc.Close()
fsub, err := js.SubscribeSync("foo")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer fsub.Unsubscribe()
bsub, err := js.SubscribeSync("bar")
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()
}
checkState := func(expected uint64) {
t.Helper()
si, err := js.StreamInfo("DC")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs != expected {
t.Fatalf("Expected %d msgs, got %d", expected, si.State.Msgs)
}
}
sendMsg("foo")
checkState(1)
getAndAck(fsub)
checkState(0)
sendMsg("bar")
sendMsg("foo")
checkState(2)
getAndAck(bsub)
checkState(1)
getAndAck(fsub)
checkState(0)
})
}
}
func TestJetStreamInterestRetentionWithWildcardsAndFilteredConsumers(t *testing.T) {
msc := StreamConfig{
Name: "DCWC",
Subjects: []string{"foo.*"},
Storage: MemoryStorage,
Retention: InterestPolicy,
}
fsc := msc
fsc.Storage = FileStorage
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &msc},
{"FileStore", &fsc},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Send 10 msgs
for i := 0; i < 10; i++ {
sendStreamMsg(t, nc, "foo.bar", "Hello World!")
}
if state := mset.state(); state.Msgs != 0 {
t.Fatalf("Expected %d messages, got %d", 0, state.Msgs)
}
cfg := &ConsumerConfig{Durable: "ddd", FilterSubject: "foo.bar", AckPolicy: AckExplicit}
o, err := mset.addConsumer(cfg)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer o.delete()
sendStreamMsg(t, nc, "foo.bar", "Hello World!")
if state := mset.state(); state.Msgs != 1 {
t.Fatalf("Expected %d message, got %d", 1, state.Msgs)
} else if state.FirstSeq != 11 {
t.Fatalf("Expected %d for first seq, got %d", 11, state.FirstSeq)
}
// Now send to foo.baz, which has no interest, so we should not hold onto this message.
sendStreamMsg(t, nc, "foo.baz", "Hello World!")
if state := mset.state(); state.Msgs != 1 {
t.Fatalf("Expected %d message, got %d", 1, state.Msgs)
}
})
}
}
func TestJetStreamInterestRetentionStreamWithDurableRestart(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &StreamConfig{Name: "IK", Storage: MemoryStorage, Retention: InterestPolicy}},
{"FileStore", &StreamConfig{Name: "IK", Storage: FileStorage, Retention: InterestPolicy}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
checkNumMsgs := func(numExpected int) {
t.Helper()
checkFor(t, time.Second, 50*time.Millisecond, func() error {
if state := mset.state(); state.Msgs != uint64(numExpected) {
return fmt.Errorf("Expected %d messages, got %d", numExpected, state.Msgs)
}
return nil
})
}
nc := clientConnectToServer(t, s)
defer nc.Close()
sub, _ := nc.SubscribeSync(nats.NewInbox())
nc.Flush()
cfg := &ConsumerConfig{Durable: "ivan", DeliverPolicy: DeliverNew, DeliverSubject: sub.Subject, AckPolicy: AckNone}
o, _ := mset.addConsumer(cfg)
sendStreamMsg(t, nc, "IK", "M1")
sendStreamMsg(t, nc, "IK", "M2")
checkSubPending := func(numExpected int) {
t.Helper()
checkFor(t, 200*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != numExpected {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, numExpected)
}
return nil
})
}
checkSubPending(2)
checkNumMsgs(0)
// Now stop the subscription.
sub.Unsubscribe()
checkFor(t, 200*time.Millisecond, 10*time.Millisecond, func() error {
if o.isActive() {
return fmt.Errorf("Still active consumer")
}
return nil
})
sendStreamMsg(t, nc, "IK", "M3")
sendStreamMsg(t, nc, "IK", "M4")
checkNumMsgs(2)
// Now restart the durable.
sub, _ = nc.SubscribeSync(nats.NewInbox())
nc.Flush()
cfg.DeliverSubject = sub.Subject
if o, err = mset.addConsumer(cfg); err != nil {
t.Fatalf("Error re-establishing the durable consumer: %v", err)
}
checkSubPending(2)
for _, expected := range []string{"M3", "M4"} {
if m, err := sub.NextMsg(time.Second); err != nil {
t.Fatalf("Unexpected error: %v", err)
} else if string(m.Data) != expected {
t.Fatalf("Expected %q, got %q", expected, m.Data)
}
}
// Should all be gone now.
checkNumMsgs(0)
// Now restart again and make sure we do not get any messages.
sub.Unsubscribe()
checkFor(t, 200*time.Millisecond, 10*time.Millisecond, func() error {
if o.isActive() {
return fmt.Errorf("Still active consumer")
}
return nil
})
o.delete()
sub, _ = nc.SubscribeSync(nats.NewInbox())
nc.Flush()
cfg.DeliverSubject = sub.Subject
cfg.AckPolicy = AckExplicit // Set ack
if o, err = mset.addConsumer(cfg); err != nil {
t.Fatalf("Error re-establishing the durable consumer: %v", err)
}
time.Sleep(100 * time.Millisecond)
checkSubPending(0)
checkNumMsgs(0)
// Now queue up some messages.
for i := 1; i <= 10; i++ {
sendStreamMsg(t, nc, "IK", fmt.Sprintf("M%d", i))
}
checkNumMsgs(10)
checkSubPending(10)
// Create second consumer
sub2, _ := nc.SubscribeSync(nats.NewInbox())
nc.Flush()
cfg.DeliverSubject = sub2.Subject
cfg.Durable = "derek"
o2, err := mset.addConsumer(cfg)
if err != nil {
t.Fatalf("Error creating second durable consumer: %v", err)
}
// Now queue up some messages.
for i := 11; i <= 20; i++ {
sendStreamMsg(t, nc, "IK", fmt.Sprintf("M%d", i))
}
checkNumMsgs(20)
checkSubPending(20)
// Now make sure deleting the consumers will remove messages from
// the stream since we are interest retention based.
o.delete()
checkNumMsgs(10)
o2.delete()
checkNumMsgs(0)
})
}
}
func TestJetStreamSystemLimits(t *testing.T) {
s := RunRandClientPortServer(t)
defer s.Shutdown()
if _, _, err := s.JetStreamReservedResources(); err == nil {
t.Fatalf("Expected error requesting jetstream reserved resources when not enabled")
}
// Create some accounts.
facc, _ := s.LookupOrRegisterAccount("FOO")
bacc, _ := s.LookupOrRegisterAccount("BAR")
zacc, _ := s.LookupOrRegisterAccount("BAZ")
jsconfig := &JetStreamConfig{MaxMemory: 1024, MaxStore: 8192, StoreDir: t.TempDir()}
if err := s.EnableJetStream(jsconfig); err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if rm, rd, err := s.JetStreamReservedResources(); err != nil {
t.Fatalf("Unexpected error requesting jetstream reserved resources: %v", err)
} else if rm != 0 || rd != 0 {
t.Fatalf("Expected reserved memory and store to be 0, got %d and %d", rm, rd)
}
limits := func(mem int64, store int64) map[string]JetStreamAccountLimits {
return map[string]JetStreamAccountLimits{
_EMPTY_: {
MaxMemory: mem,
MaxStore: store,
MaxStreams: -1,
MaxConsumers: -1,
},
}
}
if err := facc.EnableJetStream(limits(24, 192), nil); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Use up rest of our resources in memory
if err := bacc.EnableJetStream(limits(1000, 0), nil); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Now ask for more memory. Should error.
if err := zacc.EnableJetStream(limits(1000, 0), nil); err == nil {
t.Fatalf("Expected an error when exhausting memory resource limits")
}
// Disk too.
if err := zacc.EnableJetStream(limits(0, 10000), nil); err == nil {
t.Fatalf("Expected an error when exhausting memory resource limits")
}
facc.DisableJetStream()
bacc.DisableJetStream()
zacc.DisableJetStream()
// Make sure we unreserved resources.
if rm, rd, err := s.JetStreamReservedResources(); err != nil {
t.Fatalf("Unexpected error requesting jetstream reserved resources: %v", err)
} else if rm != 0 || rd != 0 {
t.Fatalf("Expected reserved memory and store to be 0, got %v and %v", friendlyBytes(rm), friendlyBytes(rd))
}
if err := facc.EnableJetStream(limits(24, 192), nil); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Test Adjust
lim := limits(jsconfig.MaxMemory, jsconfig.MaxStore)
l := lim[_EMPTY_]
l.MaxStreams = 10
l.MaxConsumers = 10
lim[_EMPTY_] = l
if err := facc.UpdateJetStreamLimits(lim); err != nil {
t.Fatalf("Unexpected error updating jetstream account limits: %v", err)
}
var msets []*stream
// Now test max streams and max consumers. Note max consumers is per stream.
for i := 0; i < 10; i++ {
mname := fmt.Sprintf("foo.%d", i)
mset, err := facc.addStream(&StreamConfig{Name: strconv.Itoa(i), Storage: MemoryStorage, Subjects: []string{mname}})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
msets = append(msets, mset)
}
// Remove them all
for _, mset := range msets {
mset.delete()
}
// Now try to add one with bytes limit that would exceed the account limit.
if _, err := facc.addStream(&StreamConfig{Name: "22", Storage: MemoryStorage, MaxBytes: jsconfig.MaxStore * 2}); err == nil {
t.Fatalf("Expected error adding stream over limit")
}
// Replicas can't be > 1
if _, err := facc.addStream(&StreamConfig{Name: "22", Storage: MemoryStorage, Replicas: 10}); err == nil {
t.Fatalf("Expected error adding stream over limit")
}
// Test consumers limit against account limit when the stream does not set a limit
mset, err := facc.addStream(&StreamConfig{Name: "22", Storage: MemoryStorage, Subjects: []string{"foo.22"}})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
for i := 0; i < 10; i++ {
oname := fmt.Sprintf("O:%d", i)
_, err := mset.addConsumer(&ConsumerConfig{Durable: oname, AckPolicy: AckExplicit})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
// This one should fail.
if _, err := mset.addConsumer(&ConsumerConfig{Durable: "O:22", AckPolicy: AckExplicit}); err == nil {
t.Fatalf("Expected error adding consumer over the limit")
}
// Test consumer limit against stream limit
mset.delete()
mset, err = facc.addStream(&StreamConfig{Name: "22", Storage: MemoryStorage, Subjects: []string{"foo.22"}, MaxConsumers: 5})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
for i := 0; i < 5; i++ {
oname := fmt.Sprintf("O:%d", i)
_, err := mset.addConsumer(&ConsumerConfig{Durable: oname, AckPolicy: AckExplicit})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
// This one should fail.
if _, err := mset.addConsumer(&ConsumerConfig{Durable: "O:22", AckPolicy: AckExplicit}); err == nil {
t.Fatalf("Expected error adding consumer over the limit")
}
// Test the account having smaller limits than the stream
mset.delete()
mset, err = facc.addStream(&StreamConfig{Name: "22", Storage: MemoryStorage, Subjects: []string{"foo.22"}, MaxConsumers: 10})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
l.MaxConsumers = 5
lim[_EMPTY_] = l
if err := facc.UpdateJetStreamLimits(lim); err != nil {
t.Fatalf("Unexpected error updating jetstream account limits: %v", err)
}
for i := 0; i < 5; i++ {
oname := fmt.Sprintf("O:%d", i)
_, err := mset.addConsumer(&ConsumerConfig{Durable: oname, AckPolicy: AckExplicit})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
// This one should fail.
if _, err := mset.addConsumer(&ConsumerConfig{Durable: "O:22", AckPolicy: AckExplicit}); err == nil {
t.Fatalf("Expected error adding consumer over the limit")
}
}
func TestJetStreamSystemLimitsPlacement(t *testing.T) {
const smallSystemLimit = 128
const mediumSystemLimit = smallSystemLimit * 2
const largeSystemLimit = smallSystemLimit * 3
tmpl := `
listen: 127.0.0.1:-1
server_name: %s
jetstream: {
max_mem_store: _MAXMEM_
max_file_store: _MAXFILE_
store_dir: '%s'
}
server_tags: [
_TAG_
]
leaf {
listen: 127.0.0.1:-1
}
cluster {
name: %s
listen: 127.0.0.1:%d
routes = [%s]
}
accounts { $SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] } }
`
storeCnf := func(serverName, clusterName, storeDir, conf string) string {
switch serverName {
case "S-1":
conf = strings.Replace(conf, "_MAXMEM_", fmt.Sprint(smallSystemLimit), 1)
conf = strings.Replace(conf, "_MAXFILE_", fmt.Sprint(smallSystemLimit), 1)
return strings.Replace(conf, "_TAG_", "small", 1)
case "S-2":
conf = strings.Replace(conf, "_MAXMEM_", fmt.Sprint(mediumSystemLimit), 1)
conf = strings.Replace(conf, "_MAXFILE_", fmt.Sprint(mediumSystemLimit), 1)
return strings.Replace(conf, "_TAG_", "medium", 1)
case "S-3":
conf = strings.Replace(conf, "_MAXMEM_", fmt.Sprint(largeSystemLimit), 1)
conf = strings.Replace(conf, "_MAXFILE_", fmt.Sprint(largeSystemLimit), 1)
return strings.Replace(conf, "_TAG_", "large", 1)
default:
return conf
}
}
cluster := createJetStreamClusterWithTemplateAndModHook(t, tmpl, "cluster-a", 3, storeCnf)
defer cluster.shutdown()
requestLeaderStepDown := func(clientURL string) error {
nc, err := nats.Connect(clientURL, nats.UserInfo("admin", "s3cr3t!"))
if err != nil {
return err
}
defer nc.Close()
ncResp, err := nc.Request(JSApiLeaderStepDown, nil, 3*time.Second)
if err != nil {
return err
}
var resp JSApiLeaderStepDownResponse
if err := json.Unmarshal(ncResp.Data, &resp); err != nil {
return err
}
if resp.Error != nil {
return resp.Error
}
if !resp.Success {
return fmt.Errorf("leader step down request not successful")
}
return nil
}
largeSrv := cluster.servers[2]
// Force large server to be leader
err := checkForErr(15*time.Second, 500*time.Millisecond, func() error {
if largeSrv.JetStreamIsLeader() {
return nil
}
if err := requestLeaderStepDown(largeSrv.ClientURL()); err != nil {
return err
}
return fmt.Errorf("large server is not leader")
})
if err != nil {
t.Skipf("failed to get desired layout: %s", err)
}
nc, js := jsClientConnect(t, largeSrv)
defer nc.Close()
cases := []struct {
name string
storage nats.StorageType
createMaxBytes int64
serverTag string
wantErr bool
}{
{
name: "file create large stream on small server",
storage: nats.FileStorage,
createMaxBytes: largeSystemLimit,
serverTag: "small",
wantErr: true,
},
{
name: "memory create large stream on small server",
storage: nats.MemoryStorage,
createMaxBytes: largeSystemLimit,
serverTag: "small",
wantErr: true,
},
{
name: "file create large stream on medium server",
storage: nats.FileStorage,
createMaxBytes: largeSystemLimit,
serverTag: "medium",
wantErr: true,
},
{
name: "memory create large stream on medium server",
storage: nats.MemoryStorage,
createMaxBytes: largeSystemLimit,
serverTag: "medium",
wantErr: true,
},
{
name: "file create large stream on large server",
storage: nats.FileStorage,
createMaxBytes: largeSystemLimit,
serverTag: "large",
},
{
name: "memory create large stream on large server",
storage: nats.MemoryStorage,
createMaxBytes: largeSystemLimit,
serverTag: "large",
},
}
for i := 0; i < len(cases) && !t.Failed(); i++ {
c := cases[i]
t.Run(c.name, func(st *testing.T) {
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: c.storage,
MaxBytes: c.createMaxBytes,
Placement: &nats.Placement{
Cluster: "cluster-a",
Tags: []string{c.serverTag},
},
})
if c.wantErr && err == nil {
st.Fatalf("unexpected stream create success, maxBytes=%d, tag=%s",
c.createMaxBytes, c.serverTag)
} else if !c.wantErr && err != nil {
st.Fatalf("unexpected error: %s", err)
}
if err == nil {
err = js.DeleteStream("TEST")
require_NoError(st, err)
}
})
}
// These next two tests should fail because although the stream fits in the
// large and medium server, it doesn't fit on the small server.
si, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: nats.FileStorage,
MaxBytes: smallSystemLimit + 1,
Replicas: 3,
})
if err == nil {
t.Fatalf("unexpected file stream create success, maxBytes=%d, replicas=%d",
si.Config.MaxBytes, si.Config.Replicas)
}
si, err = js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: nats.MemoryStorage,
MaxBytes: smallSystemLimit + 1,
Replicas: 3,
})
if err == nil {
t.Fatalf("unexpected memory stream create success, maxBytes=%d, replicas=%d",
si.Config.MaxBytes, si.Config.Replicas)
}
t.Run("meta info placement in request - empty request", func(t *testing.T) {
nc, err = nats.Connect(largeSrv.ClientURL(), nats.UserInfo("admin", "s3cr3t!"))
require_NoError(t, err)
defer nc.Close()
var resp JSApiLeaderStepDownResponse
ncResp, err := nc.Request(JSApiLeaderStepDown, []byte("{}"), 3*time.Second)
require_NoError(t, err)
err = json.Unmarshal(ncResp.Data, &resp)
require_NoError(t, err)
require_True(t, resp.Error == nil)
require_True(t, resp.Success)
})
t.Run("meta info placement in request - uninitialized fields", func(t *testing.T) {
nc, err = nats.Connect(largeSrv.ClientURL(), nats.UserInfo("admin", "s3cr3t!"))
require_NoError(t, err)
defer nc.Close()
cluster.waitOnClusterReadyWithNumPeers(3)
var resp JSApiLeaderStepDownResponse
req, err := json.Marshal(JSApiLeaderStepdownRequest{Placement: nil})
require_NoError(t, err)
ncResp, err := nc.Request(JSApiLeaderStepDown, req, 10*time.Second)
require_NoError(t, err)
err = json.Unmarshal(ncResp.Data, &resp)
require_NoError(t, err)
require_True(t, resp.Error == nil)
})
}
func TestJetStreamStreamLimitUpdate(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
err := s.GlobalAccount().UpdateJetStreamLimits(map[string]JetStreamAccountLimits{
_EMPTY_: {
MaxMemory: 128,
MaxStore: 128,
MaxStreams: 1,
},
})
require_NoError(t, err)
nc, js := jsClientConnect(t, s)
defer nc.Close()
for _, storage := range []nats.StorageType{nats.MemoryStorage, nats.FileStorage} {
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: storage,
MaxBytes: 32,
}
_, err = js.AddStream(cfg)
require_NoError(t, err)
// Create with same config is idempotent, and must not exceed max streams as it already exists.
_, err = js.AddStream(cfg)
require_NoError(t, err)
cfg.MaxBytes = 16
_, err = js.UpdateStream(cfg)
require_NoError(t, err)
require_NoError(t, js.DeleteStream("TEST"))
}
}
func TestJetStreamStreamStorageTrackingAndLimits(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
gacc := s.GlobalAccount()
al := map[string]JetStreamAccountLimits{
_EMPTY_: {
MaxMemory: 8192,
MaxStore: -1,
MaxStreams: -1,
MaxConsumers: -1,
},
}
if err := gacc.UpdateJetStreamLimits(al); err != nil {
t.Fatalf("Unexpected error updating jetstream account limits: %v", err)
}
mset, err := gacc.addStream(&StreamConfig{Name: "LIMITS", Storage: MemoryStorage, Retention: WorkQueuePolicy})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
toSend := 100
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "LIMITS", "Hello World!")
}
state := mset.state()
usage := gacc.JetStreamUsage()
// Make sure these are working correctly.
if state.Bytes != usage.Memory {
t.Fatalf("Expected to have stream bytes match memory usage, %d vs %d", state.Bytes, usage.Memory)
}
if usage.Streams != 1 {
t.Fatalf("Expected to have 1 stream, got %d", usage.Streams)
}
// Do second stream.
mset2, err := gacc.addStream(&StreamConfig{Name: "NUM22", Storage: MemoryStorage, Retention: WorkQueuePolicy})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset2.delete()
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "NUM22", "Hello World!")
}
stats2 := mset2.state()
usage = gacc.JetStreamUsage()
if usage.Memory != (state.Bytes + stats2.Bytes) {
t.Fatalf("Expected to track both streams, account is %v, stream1 is %v, stream2 is %v", usage.Memory, state.Bytes, stats2.Bytes)
}
// Make sure delete works.
mset2.delete()
stats2 = mset2.state()
usage = gacc.JetStreamUsage()
if usage.Memory != (state.Bytes + stats2.Bytes) {
t.Fatalf("Expected to track both streams, account is %v, stream1 is %v, stream2 is %v", usage.Memory, state.Bytes, stats2.Bytes)
}
// Now drain the first one by consuming the messages.
o, err := mset.addConsumer(workerModeConfig("WQ"))
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
for i := 0; i < toSend; i++ {
msg, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msg.Respond(nil)
}
nc.Flush()
state = mset.state()
checkFor(t, time.Second, 15*time.Millisecond, func() error {
usage = gacc.JetStreamUsage()
if usage.Memory != 0 {
return fmt.Errorf("Expected usage memory to be 0, got %d", usage.Memory)
}
return nil
})
// Now send twice the number of messages. Should receive an error at some point, and we will check usage against limits.
var errSeen string
for i := 0; i < toSend*2; i++ {
resp, _ := nc.Request("LIMITS", []byte("The quick brown fox jumped over the..."), 50*time.Millisecond)
if string(resp.Data) != OK {
errSeen = string(resp.Data)
break
}
}
if errSeen == "" {
t.Fatalf("Expected to see an error when exceeding the account limits")
}
state = mset.state()
var lim JetStreamAccountLimits
checkFor(t, time.Second, 15*time.Millisecond, func() error {
usage = gacc.JetStreamUsage()
lim = al[_EMPTY_]
if usage.Memory > uint64(lim.MaxMemory) {
return fmt.Errorf("Expected memory to not exceed limit of %d, got %d", lim.MaxMemory, usage.Memory)
}
return nil
})
// make sure that unlimited accounts work
lim.MaxMemory = -1
if err := gacc.UpdateJetStreamLimits(al); err != nil {
t.Fatalf("Unexpected error updating jetstream account limits: %v", err)
}
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "LIMITS", "Hello World!")
}
}
func TestJetStreamStreamFileTrackingAndLimits(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
gacc := s.GlobalAccount()
al := map[string]JetStreamAccountLimits{
_EMPTY_: {
MaxMemory: 8192,
MaxStore: 9600,
MaxStreams: -1,
MaxConsumers: -1,
},
}
if err := gacc.UpdateJetStreamLimits(al); err != nil {
t.Fatalf("Unexpected error updating jetstream account limits: %v", err)
}
mconfig := &StreamConfig{Name: "LIMITS", Storage: FileStorage, Retention: WorkQueuePolicy}
mset, err := gacc.addStream(mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
toSend := 100
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "LIMITS", "Hello World!")
}
state := mset.state()
usage := gacc.JetStreamUsage()
// Make sure these are working correctly.
if usage.Store != state.Bytes {
t.Fatalf("Expected to have stream bytes match the store usage, %d vs %d", usage.Store, state.Bytes)
}
if usage.Streams != 1 {
t.Fatalf("Expected to have 1 stream, got %d", usage.Streams)
}
// Do second stream.
mconfig2 := &StreamConfig{Name: "NUM22", Storage: FileStorage, Retention: WorkQueuePolicy}
mset2, err := gacc.addStream(mconfig2)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset2.delete()
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "NUM22", "Hello World!")
}
stats2 := mset2.state()
usage = gacc.JetStreamUsage()
if usage.Store != (state.Bytes + stats2.Bytes) {
t.Fatalf("Expected to track both streams, usage is %v, stream1 is %v, stream2 is %v", usage.Store, state.Bytes, stats2.Bytes)
}
// Make sure delete works.
mset2.delete()
stats2 = mset2.state()
usage = gacc.JetStreamUsage()
if usage.Store != (state.Bytes + stats2.Bytes) {
t.Fatalf("Expected to track both streams, account is %v, stream1 is %v, stream2 is %v", usage.Store, state.Bytes, stats2.Bytes)
}
// Now drain the first one by consuming the messages.
o, err := mset.addConsumer(workerModeConfig("WQ"))
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
for i := 0; i < toSend; i++ {
msg, err := nc.Request(o.requestNextMsgSubject(), nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msg.Respond(nil)
}
nc.Flush()
state = mset.state()
usage = gacc.JetStreamUsage()
if usage.Memory != 0 {
t.Fatalf("Expected usage memeory to be 0, got %d", usage.Memory)
}
// Now send twice the number of messages. Should receive an error at some point, and we will check usage against limits.
var errSeen string
for i := 0; i < toSend*2; i++ {
resp, _ := nc.Request("LIMITS", []byte("The quick brown fox jumped over the..."), 50*time.Millisecond)
if string(resp.Data) != OK {
errSeen = string(resp.Data)
break
}
}
if errSeen == "" {
t.Fatalf("Expected to see an error when exceeding the account limits")
}
state = mset.state()
usage = gacc.JetStreamUsage()
lim := al[_EMPTY_]
if usage.Memory > uint64(lim.MaxMemory) {
t.Fatalf("Expected memory to not exceed limit of %d, got %d", lim.MaxMemory, usage.Memory)
}
}
func TestJetStreamTieredLimits(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
gacc := s.GlobalAccount()
tFail := map[string]JetStreamAccountLimits{
"nottheer": {
MaxMemory: 8192,
MaxStore: 9600,
MaxStreams: -1,
MaxConsumers: -1,
},
}
if err := gacc.UpdateJetStreamLimits(tFail); err != nil {
t.Fatalf("Unexpected error updating jetstream account limits: %v", err)
}
mconfig := &StreamConfig{Name: "LIMITS", Storage: FileStorage, Retention: WorkQueuePolicy}
mset, err := gacc.addStream(mconfig)
defer mset.delete()
require_Error(t, err)
require_Contains(t, err.Error(), "no JetStream default or applicable tiered limit present")
tPass := map[string]JetStreamAccountLimits{
"R1": {
MaxMemory: 8192,
MaxStore: 9600,
MaxStreams: -1,
MaxConsumers: -1,
},
}
if err := gacc.UpdateJetStreamLimits(tPass); err != nil {
t.Fatalf("Unexpected error updating jetstream account limits: %v", err)
}
}
type obsi struct {
cfg ConsumerConfig
ack int
}
type info struct {
cfg StreamConfig
state StreamState
obs []obsi
}
func TestJetStreamSimpleFileRecovery(t *testing.T) {
base := runtime.NumGoroutine()
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
acc := s.GlobalAccount()
ostate := make(map[string]info)
nid := nuid.New()
randomSubject := func() string {
nid.RandomizePrefix()
return fmt.Sprintf("SUBJ.%s", nid.Next())
}
nc := clientConnectToServer(t, s)
defer nc.Close()
numStreams := 10
for i := 1; i <= numStreams; i++ {
msetName := fmt.Sprintf("MMS-%d", i)
subjects := []string{randomSubject(), randomSubject(), randomSubject()}
msetConfig := StreamConfig{
Name: msetName,
Storage: FileStorage,
Subjects: subjects,
MaxMsgs: 100,
}
mset, err := acc.addStream(&msetConfig)
if err != nil {
t.Fatalf("Unexpected error adding stream %q: %v", msetName, err)
}
toSend := rand.Intn(100) + 1
for n := 1; n <= toSend; n++ {
msg := fmt.Sprintf("Hello %d", n*i)
subj := subjects[rand.Intn(len(subjects))]
sendStreamMsg(t, nc, subj, msg)
}
// Create up to 5 consumers.
numObs := rand.Intn(5) + 1
var obs []obsi
for n := 1; n <= numObs; n++ {
oname := fmt.Sprintf("WQ-%d-%d", i, n)
o, err := mset.addConsumer(workerModeConfig(oname))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Now grab some messages.
toReceive := rand.Intn(toSend) + 1
rsubj := o.requestNextMsgSubject()
for r := 0; r < toReceive; r++ {
resp, err := nc.Request(rsubj, nil, time.Second)
require_NoError(t, err)
if resp != nil {
resp.Respond(nil)
}
}
obs = append(obs, obsi{o.config(), toReceive})
}
ostate[msetName] = info{mset.config(), mset.state(), obs}
}
pusage := acc.JetStreamUsage()
nc.Flush()
// Shutdown and restart and make sure things come back.
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
delta := (runtime.NumGoroutine() - base)
if delta > 3 {
return fmt.Errorf("%d Go routines still exist post Shutdown()", delta)
}
return nil
})
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
acc = s.GlobalAccount()
nusage := acc.JetStreamUsage()
if !reflect.DeepEqual(nusage, pusage) {
t.Fatalf("Usage does not match after restore: %+v vs %+v", nusage, pusage)
}
for mname, info := range ostate {
mset, err := acc.lookupStream(mname)
if err != nil {
t.Fatalf("Expected to find a stream for %q", mname)
}
if state := mset.state(); !reflect.DeepEqual(state, info.state) {
t.Fatalf("State does not match: %+v vs %+v", state, info.state)
}
if cfg := mset.config(); !reflect.DeepEqual(cfg, info.cfg) {
t.Fatalf("Configs do not match: %+v vs %+v", cfg, info.cfg)
}
// Consumers.
if mset.numConsumers() != len(info.obs) {
t.Fatalf("Number of consumers do not match: %d vs %d", mset.numConsumers(), len(info.obs))
}
for _, oi := range info.obs {
if o := mset.lookupConsumer(oi.cfg.Durable); o != nil {
if uint64(oi.ack+1) != o.nextSeq() {
t.Fatalf("Consumer next seq is not correct: %d vs %d", oi.ack+1, o.nextSeq())
}
} else {
t.Fatalf("Expected to get an consumer")
}
}
}
}
func TestJetStreamPushConsumerFlowControl(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST"}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sub, err := nc.SubscribeSync(nats.NewInbox())
require_NoError(t, err)
defer sub.Unsubscribe()
obsReq := CreateConsumerRequest{
Stream: "TEST",
Config: ConsumerConfig{
Durable: "dlc",
DeliverSubject: sub.Subject,
FlowControl: true,
Heartbeat: 5 * time.Second,
},
}
req, err := json.Marshal(obsReq)
require_NoError(t, err)
resp, err := nc.Request(fmt.Sprintf(JSApiDurableCreateT, "TEST", "dlc"), req, time.Second)
require_NoError(t, 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: %+v", ccResp.Error)
}
// Grab the low level consumer so we can manually set the fc max.
if mset, err := s.GlobalAccount().lookupStream("TEST"); err != nil {
t.Fatalf("Error looking up stream: %v", err)
} else if obs := mset.lookupConsumer("dlc"); obs == nil {
t.Fatalf("Error looking up stream: %v", err)
} else {
obs.mu.Lock()
obs.setMaxPendingBytes(16 * 1024)
obs.mu.Unlock()
}
msgSize := 1024
msg := make([]byte, msgSize)
crand.Read(msg)
sendBatch := func(n int) {
for i := 0; i < n; i++ {
if _, err := js.Publish("TEST", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
}
checkSubPending := func(numExpected int) {
t.Helper()
checkFor(t, time.Second, 100*time.Millisecond, func() error {
if nmsgs, _, err := sub.Pending(); err != nil || nmsgs != numExpected {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, numExpected)
}
return nil
})
}
sendBatch(100)
checkSubPending(2) // First four data and flowcontrol from slow start pause.
var n int
for m, err := sub.NextMsg(time.Second); err == nil; m, err = sub.NextMsg(time.Second) {
if m.Subject == "TEST" {
n++
} else {
// This should be a FC control message.
if m.Header.Get("Status") != "100" {
t.Fatalf("Expected a 100 status code, got %q", m.Header.Get("Status"))
}
if m.Header.Get("Description") != "FlowControl Request" {
t.Fatalf("Wrong description, got %q", m.Header.Get("Description"))
}
m.Respond(nil)
}
}
if n != 100 {
t.Fatalf("Expected to receive all 100 messages but got %d", n)
}
}
func TestJetStreamFlowControlRequiresHeartbeats(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
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.AddConsumer("TEST", &nats.ConsumerConfig{
Durable: "dlc",
DeliverSubject: nats.NewInbox(),
FlowControl: true,
}); err == nil || IsNatsErr(err, JSConsumerWithFlowControlNeedsHeartbeats) {
t.Fatalf("Unexpected error: %v", err)
}
}
func TestJetStreamPushConsumerIdleHeartbeats(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST"}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sub, err := nc.SubscribeSync(nats.NewInbox())
require_NoError(t, err)
defer sub.Unsubscribe()
// Test errors first
obsReq := CreateConsumerRequest{
Stream: "TEST",
Config: ConsumerConfig{
DeliverSubject: sub.Subject,
Heartbeat: time.Millisecond,
},
}
req, err := json.Marshal(obsReq)
require_NoError(t, err)
resp, err := nc.Request(fmt.Sprintf(JSApiConsumerCreateT, "TEST"), req, time.Second)
require_NoError(t, 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("Expected an error, got none")
}
// Set acceptable heartbeat.
obsReq.Config.Heartbeat = 100 * time.Millisecond
req, err = json.Marshal(obsReq)
require_NoError(t, err)
resp, err = nc.Request(fmt.Sprintf(JSApiConsumerCreateT, "TEST"), req, time.Second)
require_NoError(t, err)
ccResp.Error = nil
if err = json.Unmarshal(resp.Data, &ccResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkFor(t, time.Second, 20*time.Millisecond, func() error {
if nmsgs, _, err := sub.Pending(); err != nil || nmsgs < 9 {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, 9)
}
return nil
})
m, _ := sub.NextMsg(0)
if m.Header.Get("Status") != "100" {
t.Fatalf("Expected a 100 status code, got %q", m.Header.Get("Status"))
}
if m.Header.Get("Description") != "Idle Heartbeat" {
t.Fatalf("Wrong description, got %q", m.Header.Get("Description"))
}
}
func TestJetStreamPushConsumerIdleHeartbeatsWithFilterSubject(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo", "bar"}}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
hbC := make(chan *nats.Msg, 8)
sub, err := nc.ChanSubscribe(nats.NewInbox(), hbC)
require_NoError(t, err)
defer sub.Unsubscribe()
obsReq := CreateConsumerRequest{
Stream: "TEST",
Config: ConsumerConfig{
DeliverSubject: sub.Subject,
FilterSubject: "bar",
Heartbeat: 100 * time.Millisecond,
},
}
req, err := json.Marshal(obsReq)
require_NoError(t, err)
resp, err := nc.Request(fmt.Sprintf(JSApiConsumerCreateT, "TEST"), req, time.Second)
require_NoError(t, err)
var ccResp JSApiConsumerCreateResponse
if err = json.Unmarshal(resp.Data, &ccResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
st := time.NewTicker(10 * time.Millisecond)
defer st.Stop()
done := time.NewTimer(time.Second)
defer done.Stop()
for {
select {
case <-st.C:
js.Publish("foo", []byte("HELLO FOO"))
case <-done.C:
t.Fatalf("Expected to have seen idle heartbeats for consumer")
case <-hbC:
return
}
}
}
func TestJetStreamPushConsumerIdleHeartbeatsWithNoInterest(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST"}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
dsubj := "d.22"
hbC := make(chan *nats.Msg, 8)
sub, err := nc.ChanSubscribe("d.>", hbC)
require_NoError(t, err)
defer sub.Unsubscribe()
obsReq := CreateConsumerRequest{
Stream: "TEST",
Config: ConsumerConfig{
DeliverSubject: dsubj,
Heartbeat: 100 * time.Millisecond,
},
}
req, err := json.Marshal(obsReq)
require_NoError(t, err)
resp, err := nc.Request(fmt.Sprintf(JSApiConsumerCreateT, "TEST"), req, time.Second)
require_NoError(t, 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: %+v", ccResp.Error)
}
done := time.NewTimer(400 * time.Millisecond)
defer done.Stop()
for {
select {
case <-done.C:
return
case m := <-hbC:
if m.Header.Get("Status") == "100" {
t.Fatalf("Did not expect to see a heartbeat with no formal interest")
}
}
}
}
func TestJetStreamInfoAPIWithHeaders(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc := clientConnectToServer(t, s)
defer nc.Close()
m := nats.NewMsg(JSApiAccountInfo)
m.Header.Add("Accept-Encoding", "json")
m.Header.Add("Authorization", "s3cr3t")
m.Data = []byte("HELLO-JS!")
resp, err := nc.RequestMsg(m, time.Second)
require_NoError(t, err)
var info JSApiAccountInfoResponse
if err := json.Unmarshal(resp.Data, &info); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if info.Error != nil {
t.Fatalf("Received an error: %+v", info.Error)
}
}
func TestJetStreamRequestAPI(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc := clientConnectToServer(t, s)
defer nc.Close()
// This will get the current information about usage and limits for this account.
resp, err := nc.Request(JSApiAccountInfo, nil, time.Second)
require_NoError(t, err)
var info JSApiAccountInfoResponse
if err := json.Unmarshal(resp.Data, &info); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// JS API level should be set.
require_Equal(t, info.API.Level, JSApiLevel)
// Now create a stream.
msetCfg := StreamConfig{
Name: "MSET22",
Storage: FileStorage,
Subjects: []string{"foo", "bar", "baz"},
MaxMsgs: 100,
}
req, err := json.Marshal(msetCfg)
require_NoError(t, err)
resp, _ = nc.Request(fmt.Sprintf(JSApiStreamCreateT, msetCfg.Name), req, time.Second)
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)
}
if time.Since(scResp.Created) > time.Second {
t.Fatalf("Created time seems wrong: %v\n", scResp.Created)
}
// Check that the name in config has to match the name in the subject
resp, _ = nc.Request(fmt.Sprintf(JSApiStreamCreateT, "BOB"), req, time.Second)
scResp.Error, scResp.StreamInfo = nil, nil
if err := json.Unmarshal(resp.Data, &scResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkNatsError(t, scResp.Error, JSStreamMismatchErr)
// Check that update works.
msetCfg.Subjects = []string{"foo", "bar", "baz"}
msetCfg.MaxBytes = 2222222
req, err = json.Marshal(msetCfg)
require_NoError(t, err)
resp, _ = nc.Request(fmt.Sprintf(JSApiStreamUpdateT, msetCfg.Name), req, time.Second)
scResp.Error, scResp.StreamInfo = nil, nil
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)
}
// Check that updating a non existing stream fails
cfg := StreamConfig{
Name: "UNKNOWN_STREAM",
Storage: FileStorage,
Subjects: []string{"foo"},
}
req, err = json.Marshal(cfg)
require_NoError(t, err)
resp, _ = nc.Request(fmt.Sprintf(JSApiStreamUpdateT, cfg.Name), req, time.Second)
scResp.Error, scResp.StreamInfo = nil, nil
if err := json.Unmarshal(resp.Data, &scResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if scResp.StreamInfo != nil || scResp.Error == nil || scResp.Error.Code != 404 {
t.Fatalf("Unexpected error: %+v", scResp.Error)
}
// Now lookup info again and see that we can see the new stream.
resp, err = nc.Request(JSApiAccountInfo, nil, time.Second)
require_NoError(t, err)
if err = json.Unmarshal(resp.Data, &info); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if info.Streams != 1 {
t.Fatalf("Expected to see 1 Stream, got %d", info.Streams)
}
// Make sure list names works.
resp, err = nc.Request(JSApiStreams, nil, time.Second)
require_NoError(t, err)
var namesResponse JSApiStreamNamesResponse
if err = json.Unmarshal(resp.Data, &namesResponse); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(namesResponse.Streams) != 1 {
t.Fatalf("Expected only 1 stream but got %d", len(namesResponse.Streams))
}
if namesResponse.Total != 1 {
t.Fatalf("Expected total to be 1 but got %d", namesResponse.Total)
}
if namesResponse.Offset != 0 {
t.Fatalf("Expected offset to be 0 but got %d", namesResponse.Offset)
}
if namesResponse.Limit != JSApiNamesLimit {
t.Fatalf("Expected limit to be %d but got %d", JSApiNamesLimit, namesResponse.Limit)
}
if namesResponse.Streams[0] != msetCfg.Name {
t.Fatalf("Expected to get %q, but got %q", msetCfg.Name, namesResponse.Streams[0])
}
// Now do detailed version.
resp, err = nc.Request(JSApiStreamList, nil, time.Second)
require_NoError(t, err)
var listResponse JSApiStreamListResponse
if err = json.Unmarshal(resp.Data, &listResponse); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(listResponse.Streams) != 1 {
t.Fatalf("Expected only 1 stream but got %d", len(listResponse.Streams))
}
if listResponse.Total != 1 {
t.Fatalf("Expected total to be 1 but got %d", listResponse.Total)
}
if listResponse.Offset != 0 {
t.Fatalf("Expected offset to be 0 but got %d", listResponse.Offset)
}
if listResponse.Limit != JSApiListLimit {
t.Fatalf("Expected limit to be %d but got %d", JSApiListLimit, listResponse.Limit)
}
if listResponse.Streams[0].Config.Name != msetCfg.Name {
t.Fatalf("Expected to get %q, but got %q", msetCfg.Name, listResponse.Streams[0].Config.Name)
}
// Now send some messages, then we can poll for info on this stream.
toSend := 10
for i := 0; i < toSend; i++ {
nc.Request("foo", []byte("WELCOME JETSTREAM"), time.Second)
}
resp, err = nc.Request(fmt.Sprintf(JSApiStreamInfoT, msetCfg.Name), nil, time.Second)
require_NoError(t, err)
var msi StreamInfo
if err = json.Unmarshal(resp.Data, &msi); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if msi.State.Msgs != uint64(toSend) {
t.Fatalf("Expected to get %d msgs, got %d", toSend, msi.State.Msgs)
}
if time.Since(msi.Created) > time.Second {
t.Fatalf("Created time seems wrong: %v\n", msi.Created)
}
// Looking up one that is not there should yield an error.
resp, err = nc.Request(fmt.Sprintf(JSApiStreamInfoT, "BOB"), nil, time.Second)
require_NoError(t, err)
var bResp JSApiStreamInfoResponse
if err = json.Unmarshal(resp.Data, &bResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkNatsError(t, bResp.Error, JSStreamNotFoundErr)
// Now create a consumer.
delivery := nats.NewInbox()
obsReq := CreateConsumerRequest{
Stream: msetCfg.Name,
Config: ConsumerConfig{DeliverSubject: delivery},
}
req, err = json.Marshal(obsReq)
require_NoError(t, err)
resp, err = nc.Request(fmt.Sprintf(JSApiConsumerCreateT, msetCfg.Name), req, time.Second)
require_NoError(t, err)
var ccResp JSApiConsumerCreateResponse
if err = json.Unmarshal(resp.Data, &ccResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Ephemerals are now not rejected when there is no interest.
if ccResp.ConsumerInfo == nil || ccResp.Error != nil {
t.Fatalf("Got a bad response %+v", ccResp)
}
if time.Since(ccResp.Created) > time.Second {
t.Fatalf("Created time seems wrong: %v\n", ccResp.Created)
}
// Now create subscription and make sure we get proper response.
sub, _ := nc.SubscribeSync(delivery)
nc.Flush()
checkFor(t, 250*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != toSend {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, toSend)
}
return nil
})
// Check that we get an error if the stream name in the subject does not match the config.
resp, err = nc.Request(fmt.Sprintf(JSApiConsumerCreateT, "BOB"), req, time.Second)
require_NoError(t, err)
ccResp.Error, ccResp.ConsumerInfo = nil, nil
if err = json.Unmarshal(resp.Data, &ccResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Since we do not have interest this should have failed.
checkNatsError(t, ccResp.Error, JSStreamMismatchErr)
// Get the list of all of the consumers for our stream.
resp, err = nc.Request(fmt.Sprintf(JSApiConsumersT, msetCfg.Name), nil, time.Second)
require_NoError(t, err)
var clResponse JSApiConsumerNamesResponse
if err = json.Unmarshal(resp.Data, &clResponse); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(clResponse.Consumers) != 1 {
t.Fatalf("Expected only 1 consumer but got %d", len(clResponse.Consumers))
}
// Now let's get info about our consumer.
cName := clResponse.Consumers[0]
resp, err = nc.Request(fmt.Sprintf(JSApiConsumerInfoT, msetCfg.Name, cName), nil, time.Second)
require_NoError(t, err)
var oinfo ConsumerInfo
if err = json.Unmarshal(resp.Data, &oinfo); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Do some sanity checking.
// Must match consumer.go
const randConsumerNameLen = 8
if len(oinfo.Name) != randConsumerNameLen {
t.Fatalf("Expected ephemeral name, got %q", oinfo.Name)
}
if len(oinfo.Config.Durable) != 0 {
t.Fatalf("Expected no durable name, but got %q", oinfo.Config.Durable)
}
if oinfo.Config.DeliverSubject != delivery {
t.Fatalf("Expected to have delivery subject of %q, got %q", delivery, oinfo.Config.DeliverSubject)
}
if oinfo.Delivered.Consumer != 10 {
t.Fatalf("Expected consumer delivered sequence of 10, got %d", oinfo.Delivered.Consumer)
}
if oinfo.AckFloor.Consumer != 10 {
t.Fatalf("Expected ack floor to be 10, got %d", oinfo.AckFloor.Consumer)
}
// Now delete the consumer.
resp, _ = nc.Request(fmt.Sprintf(JSApiConsumerDeleteT, msetCfg.Name, cName), nil, time.Second)
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", ccResp)
}
// Make sure we can't create a durable using the ephemeral API endpoint.
obsReq = CreateConsumerRequest{
Stream: msetCfg.Name,
Config: ConsumerConfig{Durable: "myd", DeliverSubject: delivery},
}
req, err = json.Marshal(obsReq)
require_NoError(t, err)
resp, err = nc.Request(fmt.Sprintf(JSApiConsumerCreateT, msetCfg.Name), req, time.Second)
require_NoError(t, err)
ccResp.Error, ccResp.ConsumerInfo = nil, nil
if err = json.Unmarshal(resp.Data, &ccResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkNatsError(t, ccResp.Error, JSConsumerEphemeralWithDurableNameErr)
// Now make sure we can create a durable on the subject with the proper name.
resp, err = nc.Request(fmt.Sprintf(JSApiDurableCreateT, msetCfg.Name, obsReq.Config.Durable), req, time.Second)
ccResp.Error, ccResp.ConsumerInfo = nil, nil
if err = json.Unmarshal(resp.Data, &ccResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if ccResp.ConsumerInfo == nil || ccResp.Error != nil {
t.Fatalf("Did not receive correct response")
}
// Make sure empty durable in cfg does not work
obsReq2 := CreateConsumerRequest{
Stream: msetCfg.Name,
Config: ConsumerConfig{DeliverSubject: delivery},
}
req2, err := json.Marshal(obsReq2)
require_NoError(t, err)
resp, err = nc.Request(fmt.Sprintf(JSApiDurableCreateT, msetCfg.Name, obsReq.Config.Durable), req2, time.Second)
require_NoError(t, err)
ccResp.Error, ccResp.ConsumerInfo = nil, nil
if err = json.Unmarshal(resp.Data, &ccResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkNatsError(t, ccResp.Error, JSConsumerDurableNameNotSetErr)
// Now delete a msg.
dreq := JSApiMsgDeleteRequest{Seq: 2}
dreqj, err := json.Marshal(dreq)
require_NoError(t, err)
resp, _ = nc.Request(fmt.Sprintf(JSApiMsgDeleteT, msetCfg.Name), 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)
}
// Now purge the stream.
resp, _ = nc.Request(fmt.Sprintf(JSApiStreamPurgeT, msetCfg.Name), nil, time.Second)
var pResp JSApiStreamPurgeResponse
if err = json.Unmarshal(resp.Data, &pResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !pResp.Success || pResp.Error != nil {
t.Fatalf("Got a bad response %+v", pResp)
}
if pResp.Purged != 9 {
t.Fatalf("Expected 9 purged, got %d", pResp.Purged)
}
// Now delete the stream.
resp, _ = nc.Request(fmt.Sprintf(JSApiStreamDeleteT, msetCfg.Name), 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)
}
// Now grab stats again.
// This will get the current information about usage and limits for this account.
resp, err = nc.Request(JSApiAccountInfo, nil, time.Second)
require_NoError(t, err)
if err := json.Unmarshal(resp.Data, &info); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if info.Streams != 0 {
t.Fatalf("Expected no remaining streams, got %d", info.Streams)
}
// Test that we can send nil or an empty legal json for requests that take no args.
// We know this stream does not exist, this just checking request processing.
checkEmptyReqArg := func(arg string) {
t.Helper()
var req []byte
if len(arg) > 0 {
req = []byte(arg)
}
resp, err = nc.Request(fmt.Sprintf(JSApiStreamDeleteT, "foo_bar_baz"), req, time.Second)
var dResp JSApiStreamDeleteResponse
if err = json.Unmarshal(resp.Data, &dResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if dResp.Error == nil || dResp.Error.Code != 404 {
t.Fatalf("Got a bad response, expected a 404 response %+v", dResp.Error)
}
}
checkEmptyReqArg("")
checkEmptyReqArg("{}")
checkEmptyReqArg(" {} ")
checkEmptyReqArg(" { } ")
}
func TestJetStreamFilteredStreamNames(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc := clientConnectToServer(t, s)
defer nc.Close()
// Create some streams.
var snid int
createStream := func(subjects []string) {
t.Helper()
snid++
name := fmt.Sprintf("S-%d", snid)
sc := &StreamConfig{Name: name, Subjects: subjects}
if _, err := s.GlobalAccount().addStream(sc); err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
}
createStream([]string{"foo"}) // S1
createStream([]string{"bar"}) // S2
createStream([]string{"baz"}) // S3
createStream([]string{"foo.*", "bar.*"}) // S4
createStream([]string{"foo-1.22", "bar-1.33"}) // S5
expectStreams := func(filter string, streams []string) {
t.Helper()
req, _ := json.Marshal(&JSApiStreamNamesRequest{Subject: filter})
r, _ := nc.Request(JSApiStreams, req, time.Second)
var resp JSApiStreamNamesResponse
if err := json.Unmarshal(r.Data, &resp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(resp.Streams) != len(streams) {
t.Fatalf("Expected %d results, got %d", len(streams), len(resp.Streams))
}
}
expectStreams("foo", []string{"S1"})
expectStreams("bar", []string{"S2"})
expectStreams("baz", []string{"S3"})
expectStreams("*", []string{"S1", "S2", "S3"})
expectStreams(">", []string{"S1", "S2", "S3", "S4", "S5"})
expectStreams("*.*", []string{"S4", "S5"})
expectStreams("*.22", []string{"S4", "S5"})
}
func TestJetStreamUpdateStream(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{name: "MemoryStore",
mconfig: &StreamConfig{
Name: "foo",
Retention: LimitsPolicy,
MaxAge: time.Hour,
Storage: MemoryStorage,
Replicas: 1,
}},
{name: "FileStore",
mconfig: &StreamConfig{
Name: "foo",
Retention: LimitsPolicy,
MaxAge: time.Hour,
Storage: FileStorage,
Replicas: 1,
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
// Test basic updates. We allow changing the subjects, limits, and no_ack along with replicas(TBD w/ cluster)
cfg := *c.mconfig
// Can't change name.
cfg.Name = "bar"
if err := mset.update(&cfg); err == nil || !strings.Contains(err.Error(), "name must match") {
t.Fatalf("Expected error trying to update name")
}
// Can't change storage types.
cfg = *c.mconfig
if cfg.Storage == FileStorage {
cfg.Storage = MemoryStorage
} else {
cfg.Storage = FileStorage
}
if err := mset.update(&cfg); err == nil || !strings.Contains(err.Error(), "can not change") {
t.Fatalf("Expected error trying to change Storage")
}
// Can't change replicas > 1 for now.
cfg = *c.mconfig
cfg.Replicas = 10
if err := mset.update(&cfg); err == nil || !strings.Contains(err.Error(), "maximum replicas") {
t.Fatalf("Expected error trying to change Replicas")
}
// Can't change limits policy.
cfg = *c.mconfig
cfg.Retention = WorkQueuePolicy
if err := mset.update(&cfg); err == nil || !strings.Contains(err.Error(), "can not change") {
t.Fatalf("Expected error trying to change Retention")
}
// Now test changing limits.
nc := clientConnectToServer(t, s)
defer nc.Close()
pending := uint64(100)
for i := uint64(0); i < pending; i++ {
sendStreamMsg(t, nc, "foo", "0123456789")
}
pendingBytes := mset.state().Bytes
checkPending := func(msgs, bts uint64) {
t.Helper()
state := mset.state()
if state.Msgs != msgs {
t.Fatalf("Expected %d messages, got %d", msgs, state.Msgs)
}
if state.Bytes != bts {
t.Fatalf("Expected %d bytes, got %d", bts, state.Bytes)
}
}
checkPending(pending, pendingBytes)
// Update msgs to higher.
cfg = *c.mconfig
cfg.MaxMsgs = int64(pending * 2)
if err := mset.update(&cfg); err != nil {
t.Fatalf("Unexpected error %v", err)
}
if mset.config().MaxMsgs != cfg.MaxMsgs {
t.Fatalf("Expected the change to take effect, %d vs %d", mset.config().MaxMsgs, cfg.MaxMsgs)
}
checkPending(pending, pendingBytes)
// Update msgs to lower.
cfg = *c.mconfig
cfg.MaxMsgs = int64(pending / 2)
if err := mset.update(&cfg); err != nil {
t.Fatalf("Unexpected error %v", err)
}
if mset.config().MaxMsgs != cfg.MaxMsgs {
t.Fatalf("Expected the change to take effect, %d vs %d", mset.config().MaxMsgs, cfg.MaxMsgs)
}
checkPending(pending/2, pendingBytes/2)
// Now do bytes.
cfg = *c.mconfig
cfg.MaxBytes = int64(pendingBytes / 4)
if err := mset.update(&cfg); err != nil {
t.Fatalf("Unexpected error %v", err)
}
if mset.config().MaxBytes != cfg.MaxBytes {
t.Fatalf("Expected the change to take effect, %d vs %d", mset.config().MaxBytes, cfg.MaxBytes)
}
checkPending(pending/4, pendingBytes/4)
// Now do age.
cfg = *c.mconfig
cfg.MaxAge = time.Second
if err := mset.update(&cfg); err != nil {
t.Fatalf("Unexpected error %v", err)
}
// Just wait a bit for expiration.
time.Sleep(2 * time.Second)
if mset.config().MaxAge != cfg.MaxAge {
t.Fatalf("Expected the change to take effect, %d vs %d", mset.config().MaxAge, cfg.MaxAge)
}
checkPending(0, 0)
// Now put back to original.
cfg = *c.mconfig
if err := mset.update(&cfg); err != nil {
t.Fatalf("Unexpected error %v", err)
}
for i := uint64(0); i < pending; i++ {
sendStreamMsg(t, nc, "foo", "0123456789")
}
// subject changes.
// Add in a subject first.
cfg = *c.mconfig
cfg.Subjects = []string{"foo", "bar"}
if err := mset.update(&cfg); err != nil {
t.Fatalf("Unexpected error %v", err)
}
// Make sure we can still send to foo.
sendStreamMsg(t, nc, "foo", "0123456789")
// And we can now send to bar.
sendStreamMsg(t, nc, "bar", "0123456789")
// Now delete both and change to baz only.
cfg.Subjects = []string{"baz"}
if err := mset.update(&cfg); err != nil {
t.Fatalf("Unexpected error %v", err)
}
// Make sure we do not get response acks for "foo" or "bar".
if resp, err := nc.Request("foo", nil, 25*time.Millisecond); err == nil || resp != nil {
t.Fatalf("Expected no response from jetstream for deleted subject: %q", "foo")
}
if resp, err := nc.Request("bar", nil, 25*time.Millisecond); err == nil || resp != nil {
t.Fatalf("Expected no response from jetstream for deleted subject: %q", "bar")
}
// Make sure we can send to "baz"
sendStreamMsg(t, nc, "baz", "0123456789")
if nmsgs := mset.state().Msgs; nmsgs != pending+3 {
t.Fatalf("Expected %d msgs, got %d", pending+3, nmsgs)
}
// FileStore restarts for config save.
cfg = *c.mconfig
if cfg.Storage == FileStorage {
cfg.Subjects = []string{"foo", "bar"}
cfg.MaxMsgs = 2222
cfg.MaxBytes = 3333333
cfg.MaxAge = 22 * time.Hour
if err := mset.update(&cfg); err != nil {
t.Fatalf("Unexpected error %v", err)
}
// Pull since certain defaults etc are set in processing.
cfg = mset.config()
// Restart the
// Capture port since it was dynamic.
u, _ := url.Parse(s.ClientURL())
port, _ := strconv.Atoi(u.Port())
// Stop current
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
// Restart.
s = RunJetStreamServerOnPort(port, sd)
defer s.Shutdown()
mset, err = s.GlobalAccount().lookupStream(cfg.Name)
if err != nil {
t.Fatalf("Expected to find a stream for %q", cfg.Name)
}
restored_cfg := mset.config()
if !reflect.DeepEqual(cfg, restored_cfg) {
t.Fatalf("restored configuration does not match: \n%+v\n vs \n%+v", restored_cfg, cfg)
}
}
})
}
}
func TestJetStreamDeleteMsg(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{name: "MemoryStore",
mconfig: &StreamConfig{
Name: "foo",
Retention: LimitsPolicy,
MaxAge: time.Hour,
Storage: MemoryStorage,
Replicas: 1,
}},
{name: "FileStore",
mconfig: &StreamConfig{
Name: "foo",
Retention: LimitsPolicy,
MaxAge: time.Hour,
Storage: FileStorage,
Replicas: 1,
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
pubTen := func() {
t.Helper()
for i := 0; i < 10; i++ {
js.Publish("foo", []byte("Hello World!"))
}
}
pubTen()
state := mset.state()
if state.Msgs != 10 {
t.Fatalf("Expected 10 messages, got %d", state.Msgs)
}
bytesPerMsg := state.Bytes / 10
if bytesPerMsg == 0 {
t.Fatalf("Expected non-zero bytes for msg size")
}
deleteAndCheck := func(seq, expectedFirstSeq uint64) {
t.Helper()
beforeState := mset.state()
if removed, _ := mset.deleteMsg(seq); !removed {
t.Fatalf("Expected the delete of sequence %d to succeed", seq)
}
expectedState := beforeState
expectedState.Msgs--
expectedState.Bytes -= bytesPerMsg
expectedState.FirstSeq = expectedFirstSeq
sm, err := mset.getMsg(expectedFirstSeq)
if err != nil {
t.Fatalf("Error fetching message for seq: %d - %v", expectedFirstSeq, err)
}
expectedState.FirstTime = sm.Time
expectedState.Deleted = nil
expectedState.NumDeleted = 0
afterState := mset.state()
afterState.Deleted = nil
afterState.NumDeleted = 0
// Ignore first time in this test.
if !reflect.DeepEqual(afterState, expectedState) {
t.Fatalf("Stats not what we expected. Expected %+v, got %+v\n", expectedState, afterState)
}
}
// Delete one from the middle
deleteAndCheck(5, 1)
// Now make sure sequences are updated properly.
// Delete first msg.
deleteAndCheck(1, 2)
// Now last
deleteAndCheck(10, 2)
// Now gaps.
deleteAndCheck(3, 2)
deleteAndCheck(2, 4)
mset.purge(nil)
// Put ten more one.
pubTen()
deleteAndCheck(11, 12)
deleteAndCheck(15, 12)
deleteAndCheck(16, 12)
deleteAndCheck(20, 12)
// Only file storage beyond here.
if c.mconfig.Storage == MemoryStorage {
return
}
// Capture port since it was dynamic.
u, _ := url.Parse(s.ClientURL())
port, _ := strconv.Atoi(u.Port())
sd := s.JetStreamConfig().StoreDir
// Shutdown the
s.Shutdown()
s = RunJetStreamServerOnPort(port, sd)
defer s.Shutdown()
mset, err = s.GlobalAccount().lookupStream("foo")
if err != nil {
t.Fatalf("Expected to get the stream back")
}
expected := StreamState{Msgs: 6, Bytes: 6 * bytesPerMsg, FirstSeq: 12, LastSeq: 20, NumSubjects: 1}
state = mset.state()
state.FirstTime, state.LastTime, state.Deleted, state.NumDeleted = time.Time{}, time.Time{}, nil, 0
if !reflect.DeepEqual(expected, state) {
t.Fatalf("State not what we expected. Expected %+v, got %+v\n", expected, state)
}
// Now create an consumer and make sure we get the right sequence.
nc = clientConnectToServer(t, s)
defer nc.Close()
delivery := nats.NewInbox()
sub, _ := nc.SubscribeSync(delivery)
nc.Flush()
o, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: delivery, FilterSubject: "foo"})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
expectedStoreSeq := []uint64{12, 13, 14, 17, 18, 19}
for i := 0; i < 6; i++ {
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if o.streamSeqFromReply(m.Reply) != expectedStoreSeq[i] {
t.Fatalf("Expected store seq of %d, got %d", expectedStoreSeq[i], o.streamSeqFromReply(m.Reply))
}
}
})
}
}
// https://github.com/nats-io/jetstream/issues/396
func TestJetStreamLimitLockBug(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{name: "MemoryStore",
mconfig: &StreamConfig{
Name: "foo",
Retention: LimitsPolicy,
MaxMsgs: 10,
Storage: MemoryStorage,
Replicas: 1,
}},
{name: "FileStore",
mconfig: &StreamConfig{
Name: "foo",
Retention: LimitsPolicy,
MaxMsgs: 10,
Storage: FileStorage,
Replicas: 1,
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
nc := clientConnectToServer(t, s)
defer nc.Close()
for i := 0; i < 100; i++ {
sendStreamMsg(t, nc, "foo", "ok")
}
state := mset.state()
if state.Msgs != 10 {
t.Fatalf("Expected 10 messages, got %d", state.Msgs)
}
})
}
}
func TestJetStreamNextMsgNoInterest(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{name: "MemoryStore",
mconfig: &StreamConfig{
Name: "foo",
Retention: LimitsPolicy,
MaxAge: time.Hour,
Storage: MemoryStorage,
Replicas: 1,
}},
{name: "FileStore",
mconfig: &StreamConfig{
Name: "foo",
Retention: LimitsPolicy,
MaxAge: time.Hour,
Storage: FileStorage,
Replicas: 1,
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
cfg := &StreamConfig{Name: "foo", Storage: FileStorage}
mset, err := s.GlobalAccount().addStream(cfg)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
nc := clientConnectWithOldRequest(t, s)
defer nc.Close()
// Now create an consumer and make sure it functions properly.
o, err := mset.addConsumer(workerModeConfig("WQ"))
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
nextSubj := o.requestNextMsgSubject()
// Queue up a worker but use a short time out.
if _, err := nc.Request(nextSubj, nil, time.Millisecond); err != nats.ErrTimeout {
t.Fatalf("Expected a timeout error and no response with acks suppressed")
}
// Now send a message, the worker from above will still be known but we want to make
// sure the system detects that so we will do a request for next msg right behind it.
nc.Publish("foo", []byte("OK"))
if msg, err := nc.Request(nextSubj, nil, 5*time.Millisecond); err != nil {
t.Fatalf("Unexpected error: %v", err)
} else {
msg.Respond(nil) // Ack
}
// Now queue up 10 workers.
for i := 0; i < 10; i++ {
if _, err := nc.Request(nextSubj, nil, time.Microsecond); err != nats.ErrTimeout {
t.Fatalf("Expected a timeout error and no response with acks suppressed")
}
}
// Now publish ten messages.
for i := 0; i < 10; i++ {
nc.Publish("foo", []byte("OK"))
}
nc.Flush()
for i := 0; i < 10; i++ {
if msg, err := nc.Request(nextSubj, nil, 10*time.Millisecond); err != nil {
t.Fatalf("Unexpected error for %d: %v", i, err)
} else {
msg.Respond(nil) // Ack
}
}
nc.Flush()
checkFor(t, time.Second, 10*time.Millisecond, func() error {
ostate := o.info()
if ostate.AckFloor.Stream != 11 || ostate.NumAckPending > 0 {
return fmt.Errorf("Inconsistent ack state: %+v", ostate)
}
return nil
})
})
}
}
func TestJetStreamMsgHeaders(t *testing.T) {
cases := []struct {
name string
mconfig *StreamConfig
}{
{name: "MemoryStore",
mconfig: &StreamConfig{
Name: "foo",
Retention: LimitsPolicy,
MaxAge: time.Hour,
Storage: MemoryStorage,
Replicas: 1,
}},
{name: "FileStore",
mconfig: &StreamConfig{
Name: "foo",
Retention: LimitsPolicy,
MaxAge: time.Hour,
Storage: FileStorage,
Replicas: 1,
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
m := nats.NewMsg("foo")
m.Header.Add("Accept-Encoding", "json")
m.Header.Add("Authorization", "s3cr3t")
m.Data = []byte("Hello JetStream Headers - #1!")
nc.PublishMsg(m)
nc.Flush()
checkFor(t, time.Second*2, time.Millisecond*250, func() error {
state := mset.state()
if state.Msgs != 1 {
return fmt.Errorf("Expected 1 message, got %d", state.Msgs)
}
if state.Bytes == 0 {
return fmt.Errorf("Expected non-zero bytes")
}
return nil
})
// Now access raw from stream.
sm, err := mset.getMsg(1)
if err != nil {
t.Fatalf("Unexpected error getting stored message: %v", err)
}
// Calculate the []byte version of the headers.
var b bytes.Buffer
b.WriteString("NATS/1.0\r\n")
http.Header(m.Header).Write(&b)
b.WriteString("\r\n")
hdr := b.Bytes()
if !bytes.Equal(sm.Header, hdr) {
t.Fatalf("Message headers do not match, %q vs %q", hdr, sm.Header)
}
if !bytes.Equal(sm.Data, m.Data) {
t.Fatalf("Message data do not match, %q vs %q", m.Data, sm.Data)
}
// Now do consumer based.
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc.Flush()
o, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: sub.Subject})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
cm, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Error getting message: %v", err)
}
// Check the message.
// Check out original headers.
if cm.Header.Get("Accept-Encoding") != "json" ||
cm.Header.Get("Authorization") != "s3cr3t" {
t.Fatalf("Original headers not present")
}
if !bytes.Equal(m.Data, cm.Data) {
t.Fatalf("Message payloads are not the same: %q vs %q", cm.Data, m.Data)
}
})
}
}
// This will be testing our ability to conditionally rewrite subjects for last mile
// when working with JetStream. Consumers receive messages that have their subjects
// rewritten to match the original subject. NATS routing is all subject based except
// for the last mile to the client.
func TestJetStreamSingleInstanceRemoteAccess(t *testing.T) {
ca := createClusterWithName(t, "A", 1)
defer shutdownCluster(ca)
cb := createClusterWithName(t, "B", 1, ca)
defer shutdownCluster(cb)
// Connect our leafnode server to cluster B.
opts := cb.opts[rand.Intn(len(cb.opts))]
s, _ := runSolicitLeafServer(opts)
defer s.Shutdown()
checkLeafNodeConnected(t, s)
if err := s.EnableJetStream(&JetStreamConfig{StoreDir: t.TempDir()}); err != nil {
t.Fatalf("Expected no error, got %v", err)
}
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: "foo", Storage: MemoryStorage})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
toSend := 10
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "foo", "Hello World!")
}
// Now create a push based consumer. Connected to the non-jetstream server via a random server on cluster A.
sl := ca.servers[rand.Intn(len(ca.servers))]
nc2 := clientConnectToServer(t, sl)
defer nc2.Close()
sub, _ := nc2.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
// Need to wait for interest to propagate across GW.
nc2.Flush()
time.Sleep(25 * time.Millisecond)
o, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: sub.Subject})
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
checkSubPending := func(numExpected int) {
t.Helper()
checkFor(t, 200*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != numExpected {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, numExpected)
}
return nil
})
}
checkSubPending(toSend)
checkMsg := func(m *nats.Msg, err error, i int) {
t.Helper()
if err != nil {
t.Fatalf("Got an error checking message: %v", err)
}
if m.Subject != "foo" {
t.Fatalf("Expected original subject of %q, but got %q", "foo", m.Subject)
}
// Now check that reply subject exists and has a sequence as the last token.
if seq := o.seqFromReply(m.Reply); seq != uint64(i) {
t.Fatalf("Expected sequence of %d , got %d", i, seq)
}
}
// Now check the subject to make sure its the original one.
for i := 1; i <= toSend; i++ {
m, err := sub.NextMsg(time.Second)
checkMsg(m, err, i)
}
// Now do a pull based consumer.
o, err = mset.addConsumer(workerModeConfig("p"))
if err != nil {
t.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
nextMsg := o.requestNextMsgSubject()
for i := 1; i <= toSend; i++ {
m, err := nc.Request(nextMsg, nil, time.Second)
checkMsg(m, err, i)
}
}
func clientConnectToServerWithUP(t *testing.T, opts *Options, user, pass string) *nats.Conn {
curl := fmt.Sprintf("nats://%s:%s@%s:%d", user, pass, opts.Host, opts.Port)
nc, err := nats.Connect(curl, nats.Name("JS-UP-TEST"), nats.ReconnectWait(5*time.Millisecond), nats.MaxReconnects(-1))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
return nc
}
func TestJetStreamCanNotEnableOnSystemAccount(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
sa := s.SystemAccount()
if err := sa.EnableJetStream(nil, nil); err == nil {
t.Fatalf("Expected an error trying to enable on the system account")
}
}
func TestJetStreamMultipleAccountsBasics(t *testing.T) {
tdir := t.TempDir()
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 64GB, max_file_store: 10TB, store_dir: %q}
accounts: {
A: {
jetstream: enabled
users: [ {user: ua, password: pwd} ]
},
B: {
jetstream: {max_mem: 1GB, max_store: 1TB, max_streams: 10, max_consumers: 1k}
users: [ {user: ub, password: pwd} ]
},
C: {
users: [ {user: uc, password: pwd} ]
},
}
`, tdir)))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
if !s.JetStreamEnabled() {
t.Fatalf("Expected JetStream to be enabled")
}
nca := clientConnectToServerWithUP(t, opts, "ua", "pwd")
defer nca.Close()
ncb := clientConnectToServerWithUP(t, opts, "ub", "pwd")
defer ncb.Close()
resp, err := ncb.Request(JSApiAccountInfo, nil, time.Second)
require_NoError(t, err)
var info JSApiAccountInfoResponse
if err := json.Unmarshal(resp.Data, &info); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
limits := info.Limits
if limits.MaxStreams != 10 {
t.Fatalf("Expected 10 for MaxStreams, got %d", limits.MaxStreams)
}
if limits.MaxConsumers != 1000 {
t.Fatalf("Expected MaxConsumers of %d, got %d", 1000, limits.MaxConsumers)
}
gb := int64(1024 * 1024 * 1024)
if limits.MaxMemory != gb {
t.Fatalf("Expected MaxMemory to be 1GB, got %d", limits.MaxMemory)
}
if limits.MaxStore != 1024*gb {
t.Fatalf("Expected MaxStore to be 1TB, got %d", limits.MaxStore)
}
ncc := clientConnectToServerWithUP(t, opts, "uc", "pwd")
defer ncc.Close()
expectNotEnabled := func(resp *nats.Msg, err error) {
t.Helper()
if err != nil {
t.Fatalf("Unexpected error requesting enabled status: %v", err)
}
if resp == nil {
t.Fatalf("No response, possible timeout?")
}
var iResp JSApiAccountInfoResponse
if err := json.Unmarshal(resp.Data, &iResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if iResp.Error == nil {
t.Fatalf("Expected an error on not enabled account")
}
}
// Check C is not enabled. We expect a negative response, not a timeout.
expectNotEnabled(ncc.Request(JSApiAccountInfo, nil, 250*time.Millisecond))
// Now do simple reload and check that we do the right thing. Testing enable and disable and also change in limits
newConf := []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 64GB, max_file_store: 10TB, store_dir: %q}
accounts: {
A: {
jetstream: disabled
users: [ {user: ua, password: pwd} ]
},
B: {
jetstream: {max_mem: 32GB, max_store: 512GB, max_streams: 100, max_consumers: 4k}
users: [ {user: ub, password: pwd} ]
},
C: {
jetstream: {max_mem: 1GB, max_store: 1TB, max_streams: 10, max_consumers: 1k}
users: [ {user: uc, password: pwd} ]
},
}
`, tdir))
if err := os.WriteFile(conf, newConf, 0600); err != nil {
t.Fatalf("Error rewriting server's config file: %v", err)
}
if err := s.Reload(); err != nil {
t.Fatalf("Error on server reload: %v", err)
}
expectNotEnabled(nca.Request(JSApiAccountInfo, nil, 250*time.Millisecond))
resp, _ = ncb.Request(JSApiAccountInfo, nil, 250*time.Millisecond)
if err := json.Unmarshal(resp.Data, &info); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if info.Error != nil {
t.Fatalf("Expected JetStream to be enabled, got %+v", info.Error)
}
resp, _ = ncc.Request(JSApiAccountInfo, nil, 250*time.Millisecond)
if err := json.Unmarshal(resp.Data, &info); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if info.Error != nil {
t.Fatalf("Expected JetStream to be enabled, got %+v", info.Error)
}
// Now check that limits have been updated.
// Account B
resp, err = ncb.Request(JSApiAccountInfo, nil, time.Second)
require_NoError(t, err)
if err := json.Unmarshal(resp.Data, &info); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
limits = info.Limits
if limits.MaxStreams != 100 {
t.Fatalf("Expected 100 for MaxStreams, got %d", limits.MaxStreams)
}
if limits.MaxConsumers != 4000 {
t.Fatalf("Expected MaxConsumers of %d, got %d", 4000, limits.MaxConsumers)
}
if limits.MaxMemory != 32*gb {
t.Fatalf("Expected MaxMemory to be 32GB, got %d", limits.MaxMemory)
}
if limits.MaxStore != 512*gb {
t.Fatalf("Expected MaxStore to be 512GB, got %d", limits.MaxStore)
}
// Account C
resp, err = ncc.Request(JSApiAccountInfo, nil, time.Second)
require_NoError(t, err)
if err := json.Unmarshal(resp.Data, &info); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
limits = info.Limits
if limits.MaxStreams != 10 {
t.Fatalf("Expected 10 for MaxStreams, got %d", limits.MaxStreams)
}
if limits.MaxConsumers != 1000 {
t.Fatalf("Expected MaxConsumers of %d, got %d", 1000, limits.MaxConsumers)
}
if limits.MaxMemory != gb {
t.Fatalf("Expected MaxMemory to be 1GB, got %d", limits.MaxMemory)
}
if limits.MaxStore != 1024*gb {
t.Fatalf("Expected MaxStore to be 1TB, got %d", limits.MaxStore)
}
}
func TestJetStreamServerResourcesConfig(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 2GB, max_file_store: 1TB, store_dir: %q}
`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
if !s.JetStreamEnabled() {
t.Fatalf("Expected JetStream to be enabled")
}
gb := int64(1024 * 1024 * 1024)
jsc := s.JetStreamConfig()
if jsc.MaxMemory != 2*gb {
t.Fatalf("Expected MaxMemory to be %d, got %d", 2*gb, jsc.MaxMemory)
}
if jsc.MaxStore != 1024*gb {
t.Fatalf("Expected MaxStore to be %d, got %d", 1024*gb, jsc.MaxStore)
}
}
// https://github.com/nats-io/nats-server/issues/8160
func TestJetStreamProgrammaticMaxStoreAndMaxMemory(t *testing.T) {
gb := int64(1024 * 1024 * 1024)
for _, test := range []struct {
name string
maxMem int64
maxStore int64
}{
{"only-max-store", 0, 1 * gb},
{"only-max-memory", 2 * gb, 0},
{"both", 2 * gb, 1 * gb},
{"neither", 0, 0},
} {
t.Run(test.name, func(t *testing.T) {
opts := DefaultTestOptions
opts.Port = -1
opts.JetStream = true
opts.StoreDir = t.TempDir()
opts.JetStreamMaxMemory = test.maxMem
opts.JetStreamMaxStore = test.maxStore
s := RunServer(&opts)
defer s.Shutdown()
require_True(t, s.JetStreamEnabled())
jsc := s.JetStreamConfig()
if test.maxStore > 0 {
require_Equal(t, jsc.MaxStore, test.maxStore)
} else {
require_True(t, jsc.MaxStore > 0)
}
if test.maxMem > 0 {
require_Equal(t, jsc.MaxMemory, test.maxMem)
} else {
require_True(t, jsc.MaxMemory > 0)
}
})
}
}
func TestJetStreamDynConfigBothPositive(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Both flags are false because opts came from code, not a config file.
opts := s.getOpts()
require_False(t, opts.maxMemSet)
require_False(t, opts.maxStoreSet)
const maxStore = int64(1024 * 1024 * 1024)
const maxMem = int64(2 * 1024 * 1024 * 1024)
jsc := s.dynJetStreamConfig(t.TempDir(), maxStore, maxMem)
require_Equal(t, jsc.MaxStore, maxStore)
require_Equal(t, jsc.MaxMemory, maxMem)
}
func TestJetStreamConfigNegativeLimitsFallBackToDynamic(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: -1, max_file_store: -1, store_dir: %q}
`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
require_True(t, s.JetStreamEnabled())
jsc := s.JetStreamConfig()
// -1 is the "unset" sentinel: it must be replaced with dynamic limits,
require_True(t, jsc.MaxMemory > 0)
require_True(t, jsc.MaxStore > 0)
}
func TestJetStreamConfigExplicitZeroLimits(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 0, max_file_store: 0, store_dir: %q}
`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
require_True(t, s.JetStreamEnabled())
jsc := s.JetStreamConfig()
require_Equal(t, jsc.MaxMemory, 0)
require_Equal(t, jsc.MaxStore, 0)
}
// From 2.2.2 to 2.2.3 we fixed a bug that would not consistently place a jetstream directory
// under the store directory configured. However there were some cases where the directory was
// created that way and therefore 2.2.3 would start and not recognize the existing accounts,
// streams and consumers.
func TestJetStreamStoreDirectoryFix(t *testing.T) {
sd := filepath.Join(os.TempDir(), "sd_test")
defer removeDir(t, sd)
conf := createConfFile(t, []byte(fmt.Sprintf("listen: 127.0.0.1:-1\njetstream: {store_dir: %q}\n", sd)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
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("TSS")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
// Push based.
sub, err := js.SubscribeSync("TEST", nats.Durable("dlc"))
require_NoError(t, err)
defer sub.Unsubscribe()
// Now shutdown the server.
nc.Close()
s.Shutdown()
// Now move stuff up from the jetstream directory etc.
jssd := filepath.Join(sd, JetStreamStoreDir)
fis, _ := os.ReadDir(jssd)
// This will be accounts, move them up one directory.
for _, fi := range fis {
os.Rename(filepath.Join(jssd, fi.Name()), filepath.Join(sd, fi.Name()))
}
removeDir(t, jssd)
// Restart our server. Make sure our assets got moved.
s, _ = RunServerWithConfig(conf)
defer s.Shutdown()
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))
}
names = names[:0]
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 TestJetStreamPushConsumersPullError(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
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("TSS")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
// Push based.
sub, err := js.SubscribeSync("TEST")
require_NoError(t, err)
defer sub.Unsubscribe()
ci, err := sub.ConsumerInfo()
require_NoError(t, err)
// Now do a pull. Make sure we get an error.
m, err := nc.Request(fmt.Sprintf(JSApiRequestNextT, "TEST", ci.Name), nil, time.Second)
require_NoError(t, err)
if m.Header.Get("Status") != "409" {
t.Fatalf("Expected a 409 status code, got %q", m.Header.Get("Status"))
}
}
func TestJetStreamChangeConsumerType(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"test.*"}})
require_NoError(t, err)
// create pull consumer
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Name: "pull",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
// cannot update pull -> push
_, err = js.UpdateConsumer("TEST", &nats.ConsumerConfig{
Name: "pull",
AckPolicy: nats.AckExplicitPolicy,
DeliverSubject: "foo",
})
require_Contains(t, err.Error(), "can not update pull consumer to push based")
// create push consumer
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Name: "push",
AckPolicy: nats.AckExplicitPolicy,
DeliverSubject: "foo",
})
require_NoError(t, err)
// cannot change push -> pull
_, err = js.UpdateConsumer("TEST", &nats.ConsumerConfig{
Name: "push",
AckPolicy: nats.AckExplicitPolicy,
})
require_Contains(t, err.Error(), "can not update push consumer to pull based")
}
////////////////////////////////////////
// Benchmark placeholders
// TODO(dlc) - move
////////////////////////////////////////
func TestJetStreamPubPerf(t *testing.T) {
// Comment out to run, holding place for now.
t.SkipNow()
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
acc := s.GlobalAccount()
msetConfig := StreamConfig{
Name: "sr22",
Storage: FileStorage,
Subjects: []string{"foo"},
}
if _, err := acc.addStream(&msetConfig); err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
nc := clientConnectToServer(t, s)
defer nc.Close()
toSend := 5_000_000
numProducers := 5
payload := []byte("Hello World")
startCh := make(chan bool)
var wg sync.WaitGroup
for n := 0; n < numProducers; n++ {
wg.Add(1)
go func() {
defer wg.Done()
<-startCh
for i := 0; i < int(toSend)/numProducers; i++ {
nc.Publish("foo", payload)
}
nc.Flush()
}()
}
// Wait for Go routines.
time.Sleep(20 * time.Millisecond)
start := time.Now()
close(startCh)
wg.Wait()
tt := time.Since(start)
fmt.Printf("time is %v\n", tt)
fmt.Printf("%.0f msgs/sec\n", float64(toSend)/tt.Seconds())
// Stop current
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
// Restart.
start = time.Now()
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
fmt.Printf("Took %v to restart!\n", time.Since(start))
}
func TestJetStreamPubWithAsyncResponsePerf(t *testing.T) {
// Comment out to run, holding place for now.
t.SkipNow()
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
acc := s.GlobalAccount()
msetConfig := StreamConfig{
Name: "sr33",
Storage: FileStorage,
Subjects: []string{"foo"},
}
if _, err := acc.addStream(&msetConfig); err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
nc := clientConnectToServer(t, s)
defer nc.Close()
toSend := 1_000_000
payload := []byte("Hello World")
start := time.Now()
for i := 0; i < toSend; i++ {
nc.PublishRequest("foo", "bar", payload)
}
nc.Flush()
tt := time.Since(start)
fmt.Printf("time is %v\n", tt)
fmt.Printf("%.0f msgs/sec\n", float64(toSend)/tt.Seconds())
}
func TestJetStreamPubWithSyncPerf(t *testing.T) {
// Comment out to run, holding place for now.
t.SkipNow()
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "foo"})
require_NoError(t, err)
toSend := 1_000_000
payload := []byte("Hello World")
start := time.Now()
for i := 0; i < toSend; i++ {
js.Publish("foo", payload)
}
tt := time.Since(start)
fmt.Printf("time is %v\n", tt)
fmt.Printf("%.0f msgs/sec\n", float64(toSend)/tt.Seconds())
}
func TestJetStreamPubSubPerf(t *testing.T) {
// Comment out to run, holding place for now.
t.SkipNow()
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
acc := s.GlobalAccount()
msetConfig := StreamConfig{
Name: "MSET22",
Storage: FileStorage,
Subjects: []string{"foo"},
}
mset, err := acc.addStream(&msetConfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
nc := clientConnectToServer(t, s)
defer nc.Close()
var toSend = 1_000_000
var received int
done := make(chan bool)
delivery := "d"
nc.Subscribe(delivery, func(m *nats.Msg) {
received++
if received >= toSend {
done <- true
}
})
nc.Flush()
_, err = mset.addConsumer(&ConsumerConfig{
DeliverSubject: delivery,
AckPolicy: AckNone,
})
if err != nil {
t.Fatalf("Error creating consumer: %v", err)
}
payload := []byte("Hello World")
start := time.Now()
for i := 0; i < toSend; i++ {
nc.Publish("foo", payload)
}
<-done
tt := time.Since(start)
fmt.Printf("time is %v\n", tt)
fmt.Printf("%.0f msgs/sec\n", float64(toSend)/tt.Seconds())
}
func TestJetStreamAckExplicitMsgRemoval(t *testing.T) {
const burstSize = 20
for _, storageType := range []StorageType{
FileStorage,
MemoryStorage,
} {
t.Run(storageType.String(), func(t *testing.T) {
// Start a server
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Create a stream
cfg := &StreamConfig{
Name: "MY_STREAM",
Storage: storageType,
Subjects: []string{"foo.*"},
Retention: InterestPolicy,
}
mset, err := s.GlobalAccount().addStream(cfg)
require_NoError(t, err)
defer mset.delete()
// Create 2 connections
nc1 := clientConnectToServer(t, s)
defer nc1.Close()
nc2 := clientConnectToServer(t, s)
defer nc2.Close()
// Create 2 subscription inboxes (for durable consumers DeliverSubject forwarding)
sub1, err := nc1.SubscribeSync(nats.NewInbox())
require_NoError(t, err)
defer sub1.Unsubscribe()
err = nc1.Flush()
require_NoError(t, err)
sub2, err := nc2.SubscribeSync(nats.NewInbox())
require_NoError(t, err)
defer sub2.Unsubscribe()
err = nc2.Flush()
require_NoError(t, err)
// Create 2 durable consumers (that forward messages to the inboxes above)
dc1, err := mset.addConsumer(&ConsumerConfig{
Durable: "dur1",
DeliverSubject: sub1.Subject,
FilterSubject: "foo.bar",
AckPolicy: AckExplicit,
})
require_NoError(t, err)
defer dc1.delete()
dc2, err := mset.addConsumer(&ConsumerConfig{
Durable: "dur2",
DeliverSubject: sub2.Subject,
FilterSubject: "foo.bar",
AckPolicy: AckExplicit,
AckWait: 100 * time.Millisecond,
})
require_NoError(t, err)
defer dc2.delete()
// Publish N messages (with subject matching the stream created above)
for i := 1; i <= burstSize; i++ {
pubAck := sendStreamMsg(t, nc1, "foo.bar", fmt.Sprintf("msg_%d", i))
require_Equal(t, int(pubAck.Sequence), i)
}
// Check that the stream contains 2 messages
state := mset.state()
if state.Msgs != uint64(burstSize) {
t.Fatalf("Expected %d messages in stream, found %d", burstSize, state.Msgs)
}
// Receive and ack both messages from the 2 subscriptions
for subIndex, sub := range []*nats.Subscription{sub1, sub2} {
sequences := make(map[uint64]bool, burstSize)
for i := 1; i <= burstSize; i++ {
m, err := sub.NextMsg(time.Second)
require_NoError(t, err)
err = m.Respond(nil)
require_NoError(t, err)
metadata, err := m.Metadata()
require_NoError(t, err)
sequences[metadata.Sequence.Stream] = true
}
// Verify all known sequence numbers are delivered (as opposed to duplicates)
for i := uint64(1); i <= burstSize; i++ {
_, ok := sequences[i]
if !ok {
t.Fatalf("Subscriber %d/2 did not deliver message sequence %d", subIndex+1, i)
}
}
}
// Flush both connections which may have pending ACKs
err = nc1.Flush()
require_NoError(t, err)
err = nc2.Flush()
require_NoError(t, err)
// Verify all messages are eventually dropped from the stream (since all known consumers ack'd them)
checkFor(t, 3*time.Second, 100*time.Millisecond, func() error {
if state = mset.state(); state.Msgs != 0 {
return fmt.Errorf("expected empty stream, but found %d messages", state.Msgs)
}
return nil
})
// Shut down one of the subscriptions
err = sub2.Unsubscribe()
require_NoError(t, err)
err = nc2.Flush()
require_NoError(t, err)
// Publish N additional messages
for i := burstSize + 1; i <= burstSize+burstSize; i++ {
pubAck := sendStreamMsg(t, nc1, "foo.bar", fmt.Sprintf("msg_%d", i))
require_Equal(t, i, int(pubAck.Sequence))
}
// Check that the stream contains N messages
state = mset.state()
if state.Msgs != uint64(burstSize) {
t.Fatalf("Expected %d messages in stream, found %d", burstSize, state.Msgs)
}
// Receive and ack the new messages using the active subscription/consumer
sequences := make(map[uint64]bool, burstSize)
for i := 1; i <= burstSize; i++ {
m, err := sub1.NextMsg(time.Second)
require_NoError(t, err)
err = m.Respond(nil)
require_NoError(t, err)
metadata, err := m.Metadata()
require_NoError(t, err)
sequences[metadata.Sequence.Stream] = true
}
// Verify all known sequence numbers are delivered (as opposed to duplicates)
for i := uint64(burstSize) + 1; i <= burstSize+burstSize; i++ {
_, ok := sequences[i]
if !ok {
t.Fatalf("Subscriber 1/2 did not deliver message sequence %d", i)
}
}
// Create a different subscription inbox
newSub2, err := nc2.SubscribeSync(nats.NewInbox())
require_NoError(t, err)
defer newSub2.Unsubscribe()
err = nc2.Flush()
require_NoError(t, err)
// Update the second durable consumer with the new inbox subscription
dc2, err = mset.addConsumer(&ConsumerConfig{
Durable: "dur2",
DeliverSubject: newSub2.Subject,
FilterSubject: "foo.bar",
AckPolicy: AckExplicit,
AckWait: 100 * time.Millisecond,
})
require_NoError(t, err)
defer dc2.delete()
// Receive and ack the new messages using the recreated/updated subscription/consumer
sequences = make(map[uint64]bool, burstSize)
for i := 1; i <= burstSize; i++ {
m, err := newSub2.NextMsg(time.Second)
require_NoError(t, err)
err = m.Respond(nil)
require_NoError(t, err)
metadata, err := m.Metadata()
require_NoError(t, err)
sequences[metadata.Sequence.Stream] = true
}
// Verify all known sequence numbers are delivered (as opposed to duplicates)
for i := uint64(burstSize) + 1; i <= burstSize+burstSize; i++ {
_, ok := sequences[i]
if !ok {
t.Fatalf("Subscriber 2/2 did not deliver message sequence %d", i)
}
}
// Flush both connections which may have pending ACKs
err = nc1.Flush()
require_NoError(t, err)
err = nc2.Flush()
require_NoError(t, err)
// Verify all messages are eventually dropped from the stream, since all known consumers ack'd them
checkFor(t, 3*time.Second, 100*time.Millisecond, func() error {
if state = mset.state(); state.Msgs != 0 {
return fmt.Errorf("expected empty stream, but found %d messages", state.Msgs)
}
return nil
})
})
}
}
func TestJetStreamStoredMsgsDontDisappearAfterCacheExpiration(t *testing.T) {
sc := &StreamConfig{
Name: "MY_STREAM",
Storage: FileStorage,
Subjects: []string{"foo.>"},
Retention: InterestPolicy,
}
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStreamWithStore(sc, &FileStoreConfig{BlockSize: 128, CacheExpire: 15 * time.Millisecond})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc1 := clientConnectWithOldRequest(t, s)
defer nc1.Close()
// Create a durable consumers
sub, _ := nc1.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc1.Flush()
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "dur",
DeliverSubject: sub.Subject,
FilterSubject: "foo.bar",
DeliverPolicy: DeliverNew,
AckPolicy: AckExplicit,
})
if err != nil {
t.Fatalf("Unexpected error adding consumer: %v", err)
}
defer o.delete()
nc2 := clientConnectWithOldRequest(t, s)
defer nc2.Close()
sendStreamMsg(t, nc2, "foo.bar", "msg1")
msg, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Did not get message: %v", err)
}
if string(msg.Data) != "msg1" {
t.Fatalf("Unexpected message: %q", msg.Data)
}
nc1.Close()
// Get the message from the stream
getMsgSeq := func(seq uint64) {
t.Helper()
mreq := &JSApiMsgGetRequest{Seq: seq}
req, err := json.Marshal(mreq)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
smsgj, err := nc2.Request(fmt.Sprintf(JSApiMsgGetT, sc.Name), req, time.Second)
if err != nil {
t.Fatalf("Could not retrieve stream message: %v", err)
}
if strings.Contains(string(smsgj.Data), "code") {
t.Fatalf("Error: %q", smsgj.Data)
}
}
getMsgSeq(1)
time.Sleep(time.Second)
sendStreamMsg(t, nc2, "foo.bar", "msg2")
sendStreamMsg(t, nc2, "foo.bar", "msg3")
getMsgSeq(1)
getMsgSeq(2)
getMsgSeq(3)
}
func TestJetStreamDeliveryAfterServerRestart(t *testing.T) {
opts := DefaultTestOptions
opts.Port = -1
opts.JetStream = true
opts.StoreDir = t.TempDir()
s := RunServer(&opts)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(&StreamConfig{
Name: "MY_STREAM",
Storage: FileStorage,
Subjects: []string{"foo.>"},
Retention: InterestPolicy,
})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc := clientConnectToServer(t, s)
defer nc.Close()
inbox := nats.NewInbox()
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "dur",
DeliverSubject: inbox,
DeliverPolicy: DeliverNew,
AckPolicy: AckExplicit,
})
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
defer o.delete()
sub, err := nc.SubscribeSync(inbox)
if err != nil {
t.Fatalf("Error on subscribe: %v", err)
}
nc.Flush()
// Send 1 message
sendStreamMsg(t, nc, "foo.bar", "msg1")
// Make sure we receive it and ack it.
msg, err := sub.NextMsg(250 * time.Millisecond)
if err != nil {
t.Fatalf("Did not get message: %v", err)
}
// Ack it!
msg.Respond(nil)
nc.Flush()
// Shutdown client and server
nc.Close()
dir := strings.TrimSuffix(s.JetStreamConfig().StoreDir, JetStreamStoreDir)
s.Shutdown()
opts.Port = -1
opts.StoreDir = dir
s = RunServer(&opts)
defer s.Shutdown()
// Lookup stream.
mset, err = s.GlobalAccount().lookupStream("MY_STREAM")
if err != nil {
t.Fatalf("Error looking up stream: %v", err)
}
// Update consumer's deliver subject with new inbox
inbox = nats.NewInbox()
o, err = mset.addConsumer(&ConsumerConfig{
Durable: "dur",
DeliverSubject: inbox,
DeliverPolicy: DeliverNew,
AckPolicy: AckExplicit,
})
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
defer o.delete()
nc = clientConnectToServer(t, s)
defer nc.Close()
// Send 2nd message
sendStreamMsg(t, nc, "foo.bar", "msg2")
// Start sub on new inbox
sub, err = nc.SubscribeSync(inbox)
if err != nil {
t.Fatalf("Error on subscribe: %v", err)
}
nc.Flush()
// Should receive message 2.
if _, err := sub.NextMsg(500 * time.Millisecond); err != nil {
t.Fatalf("Did not get message: %v", err)
}
}
// This is for the basics of importing the ability to send to a stream and consume
// from a consumer that is pull based on push based on a well known delivery subject.
func TestJetStreamAccountImportBasics(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
no_auth_user: rip
jetstream: {max_mem_store: 64GB, max_file_store: 10TB, store_dir: %q}
accounts: {
JS: {
jetstream: enabled
users: [ {user: dlc, password: foo} ]
exports [
# This is for sending into a stream from other accounts.
{ service: "ORDERS.*" }
# This is for accessing a pull based consumer.
{ service: "$JS.API.CONSUMER.MSG.NEXT.*.*" }
# This is streaming to a delivery subject for a push based consumer.
{ stream: "deliver.ORDERS" }
# This is to ack received messages. This is a service to ack acks..
{ service: "$JS.ACK.ORDERS.*.>" }
{ service: "$JS.ACK.*.*.ORDERS.*.>" }
]
},
IU: {
users: [ {user: rip, password: bar} ]
imports [
{ service: { subject: "ORDERS.*", account: JS }, to: "my.orders.$1" }
{ service: { subject: "$JS.API.CONSUMER.MSG.NEXT.ORDERS.d", account: JS }, to: "nxt.msg" }
{ stream: { subject: "deliver.ORDERS", account: JS }, to: "d" }
{ service: { subject: "$JS.ACK.ORDERS.*.>", account: JS } }
{ service: { subject: "$JS.ACK.*.*.ORDERS.*.>", account: JS } }
]
},
}
`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
acc, err := s.LookupAccount("JS")
if err != nil {
t.Fatalf("Unexpected error looking up account: %v", err)
}
mset, err := acc.addStream(&StreamConfig{Name: "ORDERS", Subjects: []string{"ORDERS.*"}})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
// This should be the rip user, the one that imports some JS.
nc := clientConnectToServer(t, s)
defer nc.Close()
// Simple publish to a stream.
pubAck := sendStreamMsg(t, nc, "my.orders.foo", "ORDERS-1")
if pubAck.Stream != "ORDERS" || pubAck.Sequence != 1 {
t.Fatalf("Bad pubAck received: %+v", pubAck)
}
if msgs := mset.state().Msgs; msgs != 1 {
t.Fatalf("Expected 1 message, got %d", msgs)
}
total := 2
for i := 2; i <= total; i++ {
sendStreamMsg(t, nc, "my.orders.bar", fmt.Sprintf("ORDERS-%d", i))
}
if msgs := mset.state().Msgs; msgs != uint64(total) {
t.Fatalf("Expected %d messages, got %d", total, msgs)
}
// Now test access to a pull based consumer, e.g. workqueue.
o, err := mset.addConsumer(&ConsumerConfig{
Durable: "d",
AckPolicy: AckExplicit,
})
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
defer o.delete()
// We mapped the next message request, "$JS.API.CONSUMER.MSG.NEXT.ORDERS.d" -> "nxt.msg"
m, err := nc.Request("nxt.msg", nil, time.Second)
require_NoError(t, err)
if string(m.Data) != "ORDERS-1" {
t.Fatalf("Expected to receive %q, got %q", "ORDERS-1", m.Data)
}
// Now test access to a push based consumer
o, err = mset.addConsumer(&ConsumerConfig{
Durable: "p",
DeliverSubject: "deliver.ORDERS",
AckPolicy: AckExplicit,
})
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
defer o.delete()
// We remapped from above, deliver.ORDERS -> d
sub, _ := nc.SubscribeSync("d")
defer sub.Unsubscribe()
checkFor(t, 250*time.Millisecond, 10*time.Millisecond, func() error {
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != total {
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, total)
}
return nil
})
m, _ = sub.NextMsg(time.Second)
// Make sure we remapped subject correctly across the account boundary.
if m.Subject != "ORDERS.foo" {
t.Fatalf("Expected subject of %q, got %q", "ORDERS.foo", m.Subject)
}
// Now make sure we can ack messages correctly.
m.Respond(AckAck)
nc.Flush()
if info := o.info(); info.AckFloor.Consumer != 1 {
t.Fatalf("Did not receive the ack properly")
}
// Grab second one now.
m, _ = sub.NextMsg(time.Second)
// Make sure we remapped subject correctly across the account boundary.
if m.Subject != "ORDERS.bar" {
t.Fatalf("Expected subject of %q, got %q", "ORDERS.bar", m.Subject)
}
// Now make sure we can ack messages and get back an ack as well.
resp, err := nc.Request(m.Reply, nil, 100*time.Millisecond)
if resp == nil {
t.Fatalf("No response (error: %v)", err)
}
if info := o.info(); info.AckFloor.Consumer != 2 {
t.Fatalf("Did not receive the ack properly")
}
}
// This tests whether we are able to aggregate all JetStream advisory events
// from all accounts into a single account. Config for this test uses
// service imports and exports as that allows for gathering all events
// without having to know the account name and without separate entries
// for each account in aggregate account config.
// This test fails as it is not receiving the api audit event ($JS.EVENT.ADVISORY.API).
func TestJetStreamAccountImportJSAdvisoriesAsService(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen=127.0.0.1:-1
no_auth_user: pp
jetstream: {max_mem_store: 64GB, max_file_store: 10TB, store_dir: %q}
accounts {
JS {
jetstream: enabled
users: [ {user: pp, password: foo} ]
imports [
{ service: { account: AGG, subject: '$JS.EVENT.ADVISORY.ACC.JS.>' }, to: '$JS.EVENT.ADVISORY.>' }
]
}
AGG {
users: [ {user: agg, password: foo} ]
exports: [
{ service: '$JS.EVENT.ADVISORY.ACC.*.>', response: Singleton, account_token_position: 5 }
]
}
}
`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
// This should be the pp user, one which manages JetStream assets
ncJS, err := nats.Connect(s.ClientURL())
if err != nil {
t.Fatalf("Unexpected error during connect: %v", err)
}
defer ncJS.Close()
// This is the agg user, which should aggregate all JS advisory events.
ncAgg, err := nats.Connect(s.ClientURL(), nats.UserInfo("agg", "foo"))
if err != nil {
t.Fatalf("Unexpected error during connect: %v", err)
}
defer ncAgg.Close()
js, err := ncJS.JetStream()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// user from JS account should receive events on $JS.EVENT.ADVISORY.> subject
subJS, err := ncJS.SubscribeSync("$JS.EVENT.ADVISORY.>")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer subJS.Unsubscribe()
require_NoError(t, ncJS.Flush())
// user from AGG account should receive events on mapped $JS.EVENT.ADVISORY.ACC.JS.> subject (with account name)
subAgg, err := ncAgg.SubscribeSync("$JS.EVENT.ADVISORY.ACC.JS.>")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer subAgg.Unsubscribe()
require_NoError(t, ncAgg.Flush())
// add stream using JS account
// this should trigger 2 events:
// - an action event on $JS.EVENT.ADVISORY.STREAM.CREATED.ORDERS
// - an api audit event on $JS.EVENT.ADVISORY.API
_, err = js.AddStream(&nats.StreamConfig{Name: "ORDERS", Subjects: []string{"ORDERS.*"}})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
gotEvents := map[string]int{}
for i := 0; i < 2; i++ {
msg, err := subJS.NextMsg(time.Second * 2)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
gotEvents[msg.Subject]++
}
if c := gotEvents["$JS.EVENT.ADVISORY.STREAM.CREATED.ORDERS"]; c != 1 {
t.Fatalf("Should have received one advisory from $JS.EVENT.ADVISORY.STREAM.CREATED.ORDERS but got %d", c)
}
if c := gotEvents["$JS.EVENT.ADVISORY.API"]; c != 1 {
t.Fatalf("Should have received one advisory from $JS.EVENT.ADVISORY.API but got %d", c)
}
// same set of events should be received by AGG account
// on subjects containing account name (ACC.JS)
gotEvents = map[string]int{}
for i := 0; i < 2; i++ {
msg, err := subAgg.NextMsg(time.Second * 2)
require_NoError(t, err)
var adv JSAPIAudit
require_NoError(t, json.Unmarshal(msg.Data, &adv))
// Make sure we have full fidelity info via implicit share.
if adv.Client != nil {
require_True(t, adv.Client.Host != _EMPTY_)
require_True(t, adv.Client.User != _EMPTY_)
require_True(t, adv.Client.Lang != _EMPTY_)
}
gotEvents[msg.Subject]++
}
if c := gotEvents["$JS.EVENT.ADVISORY.ACC.JS.STREAM.CREATED.ORDERS"]; c != 1 {
t.Fatalf("Should have received one advisory from $JS.EVENT.ADVISORY.ACC.JS.STREAM.CREATED.ORDERS but got %d", c)
}
if c := gotEvents["$JS.EVENT.ADVISORY.ACC.JS.API"]; c != 1 {
t.Fatalf("Should have received one advisory from $JS.EVENT.ADVISORY.ACC.JS.API but got %d", c)
}
}
// This tests whether we are able to aggregate all JetStream advisory events
// from all accounts into a single account. Config for this test uses
// stream imports and exports as that allows for gathering all events
// as long as there is a separate stream import entry for each account
// in aggregate account config.
func TestJetStreamAccountImportJSAdvisoriesAsStream(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen=127.0.0.1:-1
no_auth_user: pp
jetstream: {max_mem_store: 64GB, max_file_store: 10TB, store_dir: %q}
accounts {
JS {
jetstream: enabled
users: [ {user: pp, password: foo} ]
exports [
{ stream: '$JS.EVENT.ADVISORY.>' }
]
}
AGG {
users: [ {user: agg, password: foo} ]
imports: [
{ stream: { account: JS, subject: '$JS.EVENT.ADVISORY.>' }, to: '$JS.EVENT.ADVISORY.ACC.JS.>' }
]
}
}
`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
// This should be the pp user, one which manages JetStream assets
ncJS, err := nats.Connect(s.ClientURL())
if err != nil {
t.Fatalf("Unexpected error during connect: %v", err)
}
defer ncJS.Close()
// This is the agg user, which should aggregate all JS advisory events.
ncAgg, err := nats.Connect(s.ClientURL(), nats.UserInfo("agg", "foo"))
if err != nil {
t.Fatalf("Unexpected error during connect: %v", err)
}
defer ncAgg.Close()
js, err := ncJS.JetStream()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// user from JS account should receive events on $JS.EVENT.ADVISORY.> subject
subJS, err := ncJS.SubscribeSync("$JS.EVENT.ADVISORY.>")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer subJS.Unsubscribe()
require_NoError(t, ncJS.Flush())
// user from AGG account should receive events on mapped $JS.EVENT.ADVISORY.ACC.JS.> subject (with account name)
subAgg, err := ncAgg.SubscribeSync("$JS.EVENT.ADVISORY.ACC.JS.>")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer subAgg.Unsubscribe()
require_NoError(t, ncAgg.Flush())
// add stream using JS account
// this should trigger 2 events:
// - an action event on $JS.EVENT.ADVISORY.STREAM.CREATED.ORDERS
// - an api audit event on $JS.EVENT.ADVISORY.API
_, err = js.AddStream(&nats.StreamConfig{Name: "ORDERS", Subjects: []string{"ORDERS.*"}})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
var gotAPIAdvisory, gotCreateAdvisory bool
for i := 0; i < 2; i++ {
msg, err := subJS.NextMsg(time.Second * 2)
if err != nil {
t.Fatalf("Unexpected error on JS account: %v", err)
}
switch msg.Subject {
case "$JS.EVENT.ADVISORY.STREAM.CREATED.ORDERS":
gotCreateAdvisory = true
case "$JS.EVENT.ADVISORY.API":
gotAPIAdvisory = true
default:
t.Fatalf("Unexpected subject: %q", msg.Subject)
}
}
if !gotAPIAdvisory || !gotCreateAdvisory {
t.Fatalf("Expected to have received both advisories on JS account (API advisory %v, create advisory %v)", gotAPIAdvisory, gotCreateAdvisory)
}
// same set of events should be received by AGG account
// on subjects containing account name (ACC.JS)
gotAPIAdvisory, gotCreateAdvisory = false, false
for i := 0; i < 2; i++ {
msg, err := subAgg.NextMsg(time.Second * 2)
if err != nil {
t.Fatalf("Unexpected error on AGG account: %v", err)
}
switch msg.Subject {
case "$JS.EVENT.ADVISORY.ACC.JS.STREAM.CREATED.ORDERS":
gotCreateAdvisory = true
case "$JS.EVENT.ADVISORY.ACC.JS.API":
gotAPIAdvisory = true
default:
t.Fatalf("Unexpected subject: %q", msg.Subject)
}
}
if !gotAPIAdvisory || !gotCreateAdvisory {
t.Fatalf("Expected to have received both advisories on AGG account (API advisory %v, create advisory %v)", gotAPIAdvisory, gotCreateAdvisory)
}
}
// This is for importing all of JetStream into another account for admin purposes.
func TestJetStreamAccountImportAll(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
no_auth_user: rip
jetstream: {max_mem_store: 64GB, max_file_store: 10TB, store_dir: %q}
accounts: {
JS: {
jetstream: enabled
users: [ {user: dlc, password: foo} ]
exports [ { service: "$JS.API.>" } ]
},
IU: {
users: [ {user: rip, password: bar} ]
imports [ { service: { subject: "$JS.API.>", account: JS }, to: "jsapi.>"} ]
},
}
`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
acc, err := s.LookupAccount("JS")
if err != nil {
t.Fatalf("Unexpected error looking up account: %v", err)
}
mset, err := acc.addStream(&StreamConfig{Name: "ORDERS", Subjects: []string{"ORDERS.*"}})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
// This should be the rip user, the one that imports all of JS.
nc := clientConnectToServer(t, s)
defer nc.Close()
mapSubj := func(subject string) string {
return strings.Replace(subject, "$JS.API.", "jsapi.", 1)
}
// This will get the current information about usage and limits for this account.
resp, err := nc.Request(mapSubj(JSApiAccountInfo), nil, time.Second)
require_NoError(t, err)
var info JSApiAccountInfoResponse
if err := json.Unmarshal(resp.Data, &info); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if info.Error != nil {
t.Fatalf("Unexpected error: %+v", info.Error)
}
// Lookup streams.
resp, err = nc.Request(mapSubj(JSApiStreams), nil, time.Second)
require_NoError(t, err)
var namesResponse JSApiStreamNamesResponse
if err = json.Unmarshal(resp.Data, &namesResponse); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if namesResponse.Error != nil {
t.Fatalf("Unexpected error: %+v", namesResponse.Error)
}
}
// https://github.com/nats-io/nats-server/issues/1736
func TestJetStreamServerReload(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 64GB, max_file_store: 10TB, store_dir: %q }
accounts: {
A: { users: [ {user: ua, password: pwd} ] },
B: {
jetstream: {max_mem: 1GB, max_store: 1TB, max_streams: 10, max_consumers: 1k}
users: [ {user: ub, password: pwd} ]
},
SYS: { users: [ {user: uc, password: pwd} ] },
}
no_auth_user: ub
system_account: SYS
`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
if !s.JetStreamEnabled() {
t.Fatalf("Expected JetStream to be enabled")
}
// Client for API requests.
nc := clientConnectToServer(t, s)
defer nc.Close()
checkJSAccount := func() {
t.Helper()
resp, err := nc.Request(JSApiAccountInfo, nil, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var info JSApiAccountInfoResponse
if err := json.Unmarshal(resp.Data, &info); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
checkJSAccount()
acc, err := s.LookupAccount("B")
if err != nil {
t.Fatalf("Unexpected error looking up account: %v", err)
}
mset, err := acc.addStream(&StreamConfig{Name: "22"})
require_NoError(t, err)
toSend := 10
for i := 0; i < toSend; i++ {
sendStreamMsg(t, nc, "22", fmt.Sprintf("MSG: %d", i+1))
}
if msgs := mset.state().Msgs; msgs != uint64(toSend) {
t.Fatalf("Expected %d messages, got %d", toSend, msgs)
}
if err := s.Reload(); err != nil {
t.Fatalf("Error on server reload: %v", err)
}
// Wait to get reconnected.
checkFor(t, 5*time.Second, 10*time.Millisecond, func() error {
if !nc.IsConnected() {
return fmt.Errorf("Not connected")
}
return nil
})
checkJSAccount()
sendStreamMsg(t, nc, "22", "MSG: 22")
}
func TestJetStreamConfigReloadWithGlobalAccount(t *testing.T) {
tdir := t.TempDir()
template := `
listen: 127.0.0.1:-1
authorization {
users [
{user: anonymous}
{user: user1, password: %s}
]
}
no_auth_user: anonymous
jetstream {
store_dir = %q
}
`
conf := createConfFile(t, []byte(fmt.Sprintf(template, "pwd", tdir)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
checkJSAccount := func() {
t.Helper()
if _, err := js.AccountInfo(); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
checkJSAccount()
if _, err := js.AddStream(&nats.StreamConfig{Name: "foo"}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
toSend := 10
for i := 0; i < toSend; i++ {
if _, err := js.Publish("foo", []byte(fmt.Sprintf("MSG: %d", i+1))); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
si, err := js.StreamInfo("foo")
require_NoError(t, err)
if si.State.Msgs != uint64(toSend) {
t.Fatalf("Expected %d msgs after restart, got %d", toSend, si.State.Msgs)
}
if err := os.WriteFile(conf, []byte(fmt.Sprintf(template, "pwd2", tdir)), 0666); err != nil {
t.Fatalf("Error writing config: %v", err)
}
if err := s.Reload(); err != nil {
t.Fatalf("Error during config reload: %v", err)
}
nc, js = jsClientConnect(t, s)
defer nc.Close()
// Try to add a new stream to the global account
if _, err := js.AddStream(&nats.StreamConfig{Name: "bar"}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkJSAccount()
}
// Test that we properly enforce per subject msg limits.
func TestJetStreamMaxMsgsPerSubject(t *testing.T) {
const maxPer = 5
msc := StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar", "baz.*"},
Storage: MemoryStorage,
MaxMsgsPer: maxPer,
}
fsc := msc
fsc.Storage = FileStorage
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &msc},
{"FileStore", &fsc},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
pubAndCheck := func(subj string, num int, expectedNumMsgs uint64) {
t.Helper()
for i := 0; i < num; i++ {
if _, err = js.Publish(subj, []byte("TSLA")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
si, err := js.StreamInfo("TEST")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs != expectedNumMsgs {
t.Fatalf("Expected %d msgs, got %d", expectedNumMsgs, si.State.Msgs)
}
}
pubAndCheck("foo", 1, 1)
pubAndCheck("foo", 4, 5)
// Now make sure our per subject limits kick in..
pubAndCheck("foo", 2, 5)
pubAndCheck("baz.22", 5, 10)
pubAndCheck("baz.33", 5, 15)
// We are maxed so totals should be same no matter what we add here.
pubAndCheck("baz.22", 5, 15)
pubAndCheck("baz.33", 5, 15)
// Now purge and make sure all is still good.
mset.purge(nil)
pubAndCheck("foo", 1, 1)
pubAndCheck("foo", 4, 5)
pubAndCheck("baz.22", 5, 10)
pubAndCheck("baz.33", 5, 15)
})
}
}
func TestJetStreamGetLastMsgBySubject(t *testing.T) {
for _, st := range []StorageType{FileStorage, MemoryStorage} {
t.Run(st.String(), func(t *testing.T) {
c := createJetStreamClusterExplicit(t, "JSC", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
cfg := StreamConfig{
Name: "KV",
Subjects: []string{"kv.>"},
Storage: st,
Replicas: 2,
MaxMsgsPer: 20,
}
req, err := json.Marshal(cfg)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Do manually for now.
nc.Request(fmt.Sprintf(JSApiStreamCreateT, cfg.Name), req, time.Second)
si, err := js.StreamInfo("KV")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si == nil || si.Config.Name != "KV" {
t.Fatalf("StreamInfo is not correct %+v", si)
}
for i := 0; i < 1000; i++ {
msg := []byte(fmt.Sprintf("VAL-%d", i+1))
js.PublishAsync("kv.foo", msg)
js.PublishAsync("kv.bar", msg)
js.PublishAsync("kv.baz", msg)
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
// Check that if both set that errors.
b, _ := json.Marshal(JSApiMsgGetRequest{LastFor: "kv.foo", Seq: 950})
rmsg, err := nc.Request(fmt.Sprintf(JSApiMsgGetT, "KV"), b, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %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.Error == nil {
t.Fatalf("Expected an error when both are set, got %+v", resp.Error)
}
// Need to do stream GetMsg by hand for now until Go client support lands.
getLast := func(subject string) *StoredMsg {
t.Helper()
req := &JSApiMsgGetRequest{LastFor: subject}
b, _ := json.Marshal(req)
rmsg, err := nc.Request(fmt.Sprintf(JSApiMsgGetT, "KV"), b, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %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)
}
return resp.Message
}
// Do basic checks.
basicCheck := func(subject string, expectedSeq uint64) {
sm := getLast(subject)
if sm == nil {
t.Fatalf("Expected a message but got none")
} else if string(sm.Data) != "VAL-1000" {
t.Fatalf("Wrong message payload, wanted %q but got %q", "VAL-1000", sm.Data)
} else if sm.Sequence != expectedSeq {
t.Fatalf("Wrong message sequence, wanted %d but got %d", expectedSeq, sm.Sequence)
} else if !subjectIsSubsetMatch(sm.Subject, subject) {
t.Fatalf("Wrong subject, wanted %q but got %q", subject, sm.Subject)
}
}
basicCheck("kv.foo", 2998)
basicCheck("kv.bar", 2999)
basicCheck("kv.baz", 3000)
basicCheck("kv.*", 3000)
basicCheck(">", 3000)
})
}
}
// https://github.com/nats-io/nats-server/issues/2329
func TestJetStreamGetLastMsgBySubjectAfterUpdate(t *testing.T) {
c := createJetStreamClusterExplicit(t, "JSC", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
sc := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Replicas: 2,
}
if _, err := js.AddStream(sc); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Now Update and add in other subjects.
sc.Subjects = append(sc.Subjects, "bar", "baz")
if _, err := js.UpdateStream(sc); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js.Publish("foo", []byte("OK1")) // 1
js.Publish("bar", []byte("OK1")) // 2
js.Publish("foo", []byte("OK2")) // 3
js.Publish("bar", []byte("OK2")) // 4
// Need to do stream GetMsg by hand for now until Go client support lands.
getLast := func(subject string) *StoredMsg {
t.Helper()
req := &JSApiMsgGetRequest{LastFor: subject}
b, _ := json.Marshal(req)
rmsg, err := nc.Request(fmt.Sprintf(JSApiMsgGetT, "TEST"), b, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %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)
}
return resp.Message
}
// Do basic checks.
basicCheck := func(subject string, expectedSeq uint64) {
sm := getLast(subject)
if sm == nil {
t.Fatalf("Expected a message but got none")
} else if sm.Sequence != expectedSeq {
t.Fatalf("Wrong message sequence, wanted %d but got %d", expectedSeq, sm.Sequence)
} else if !subjectIsSubsetMatch(sm.Subject, subject) {
t.Fatalf("Wrong subject, wanted %q but got %q", subject, sm.Subject)
}
}
basicCheck("foo", 3)
basicCheck("bar", 4)
}
func TestJetStreamLastSequenceBySubject(t *testing.T) {
for _, st := range []StorageType{FileStorage, MemoryStorage} {
t.Run(st.String(), func(t *testing.T) {
c := createJetStreamClusterExplicit(t, "JSC", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
cfg := StreamConfig{
Name: "KV",
Subjects: []string{"kv.>"},
Storage: st,
Replicas: 3,
MaxMsgsPer: 1,
}
req, err := json.Marshal(cfg)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Do manually for now.
m, err := nc.Request(fmt.Sprintf(JSApiStreamCreateT, cfg.Name), req, time.Second)
require_NoError(t, err)
si, err := js.StreamInfo("KV")
if err != nil {
t.Fatalf("Unexpected error: %v, respmsg: %q", err, string(m.Data))
}
if si == nil || si.Config.Name != "KV" {
t.Fatalf("StreamInfo is not correct %+v", si)
}
js.PublishAsync("kv.foo", []byte("1"))
js.PublishAsync("kv.bar", []byte("2"))
js.PublishAsync("kv.baz", []byte("3"))
select {
case <-js.PublishAsyncComplete():
case <-time.After(time.Second):
t.Fatalf("Did not receive completion signal")
}
// Now make sure we get an error if the last sequence is not correct per subject.
pubAndCheck := func(subj, seq string, ok bool) {
t.Helper()
m := nats.NewMsg(subj)
m.Data = []byte("HELLO")
m.Header.Set(JSExpectedLastSubjSeq, seq)
_, err := js.PublishMsg(m)
if ok && err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !ok && err == nil {
t.Fatalf("Expected to get an error and got none")
}
}
pubAndCheck("kv.foo", "1", true) // So last is now 4.
pubAndCheck("kv.foo", "1", false) // This should fail.
pubAndCheck("kv.bar", "2", true)
pubAndCheck("kv.bar", "5", true)
pubAndCheck("kv.xxx", "5", false)
})
}
}
func TestJetStreamLastSequenceBySubjectWithSubject(t *testing.T) {
test := func(replicas int, st StorageType) {
t.Run(fmt.Sprintf("R%d/%s", replicas, st), func(t *testing.T) {
c := createJetStreamClusterExplicit(t, "JSC", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.randomServer())
defer nc.Close()
cfg := StreamConfig{
Name: "KV",
Subjects: []string{"kv.>"},
Storage: st,
Replicas: replicas,
MaxMsgsPer: 1,
}
req, err := json.Marshal(cfg)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Do manually for now.
m, err := nc.Request(fmt.Sprintf(JSApiStreamCreateT, cfg.Name), req, time.Second)
require_NoError(t, err)
si, err := js.StreamInfo("KV")
if err != nil {
t.Fatalf("Unexpected error: %v, respmsg: %q", err, string(m.Data))
}
if si == nil || si.Config.Name != "KV" {
t.Fatalf("StreamInfo is not correct %+v", si)
}
js.PublishAsync("kv.1.foo", []byte("1:1")) // Last is 1 for kv.1.foo; 1 for kv.1.*;
js.PublishAsync("kv.1.bar", []byte("1:2")) // Last is 2 for kv.1.bar; 2 for kv.1.*;
js.PublishAsync("kv.2.foo", []byte("2:1")) // Last is 3 for kv.2.foo; 3 for kv.2.*;
js.PublishAsync("kv.3.bar", []byte("3:1")) // Last is 4 for kv.3.bar; 4 for kv.3.*;
js.PublishAsync("kv.1.baz", []byte("1:3")) // Last is 5 for kv.1.baz; 5 for kv.1.*;
js.PublishAsync("kv.1.bar", []byte("1:4")) // Last is 6 for kv.1.baz; 6 for kv.1.*;
js.PublishAsync("kv.2.baz", []byte("2:2")) // Last is 7 for kv.2.baz; 7 for kv.2.*;
select {
case <-js.PublishAsyncComplete():
case <-time.After(time.Second):
t.Fatalf("Did not receive completion signal")
}
// Now make sure we get an error if the last sequence is not correct per subject.
pubAndCheck := func(subj, filterSubject, seq string, ok bool) {
t.Helper()
m := nats.NewMsg(subj)
m.Data = []byte("HELLO")
// Expect last to be seq.
m.Header.Set(JSExpectedLastSubjSeq, seq)
// Constrain the sequence restriction to a specific subject
// e.g. "kv.1.*" for kv.1.foo, kv.1.bar, kv.1.baz; kv.2.* for kv.2.foo, kv.2.baz; kv.3.* for kv.3.bar
m.Header.Set(JSExpectedLastSubjSeqSubj, filterSubject)
_, err := js.PublishMsg(m)
if ok && err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !ok && err == nil {
t.Fatalf("Expected to get an error and got none")
}
}
pubAndCheck("kv.1.foo", "kv.1.*", "0", false)
pubAndCheck("kv.1.bar", "kv.1.*", "0", false)
pubAndCheck("kv.1.xxx", "kv.1.*", "0", false)
pubAndCheck("kv.1.foo", "kv.1.*", "1", false)
pubAndCheck("kv.1.bar", "kv.1.*", "1", false)
pubAndCheck("kv.1.xxx", "kv.1.*", "1", false)
pubAndCheck("kv.2.foo", "kv.2.*", "1", false)
pubAndCheck("kv.2.bar", "kv.2.*", "1", false)
pubAndCheck("kv.2.xxx", "kv.2.*", "1", false)
pubAndCheck("kv.1.bar", "kv.1.*", "2", false)
pubAndCheck("kv.1.bar", "kv.1.*", "3", false)
pubAndCheck("kv.1.bar", "kv.1.*", "4", false)
pubAndCheck("kv.1.bar", "kv.1.*", "5", false)
pubAndCheck("kv.1.bar", "kv.1.*", "6", true) // Last is 8 for kv.1.bar; 8 for kv.1.*;
pubAndCheck("kv.1.baz", "kv.1.*", "2", false)
pubAndCheck("kv.1.bar", "kv.1.*", "7", false)
pubAndCheck("kv.1.xxx", "kv.1.*", "8", true) // Last is 9 for kv.1.xxx; 9 for kv.1.*;
pubAndCheck("kv.2.foo", "kv.2.*", "2", false)
pubAndCheck("kv.2.foo", "kv.2.*", "7", true) // Last is 10 for kv.2.foo; 10 for kv.2.*;
pubAndCheck("kv.xxx", "kv.*", "0", true) // Last is 0 for kv.xxx; 0 for kv.*;
pubAndCheck("kv.xxx", "kv.*.*", "0", false) // Last is 11 for kv.xxx; 11 for kv.*.*;
pubAndCheck("kv.3.xxx", "kv.3.*", "4", true) // Last is 12 for kv.3.xxx; 12 for kv.3.*;
pubAndCheck("kv.3.xyz", "kv.3.*", "12", true) // Last is 13 for kv.3.xyz; 13 for kv.3.*;
// When using the last-subj-seq-subj header, but the sequence header is missing.
m = nats.NewMsg("kv.invalid")
m.Data = []byte("HELLO")
m.Header.Set(JSExpectedLastSubjSeqSubj, "kv.invalid")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSStreamExpectedLastSeqPerSubjectInvalidError())
})
}
for _, replicas := range []int{1, 3} {
for _, st := range []StorageType{FileStorage, MemoryStorage} {
test(replicas, st)
}
}
}
func TestJetStreamFilteredConsumersWithWiderFilter(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Origin
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar", "baz", "N.*"},
})
require_NoError(t, err)
// Add in some messages.
js.Publish("foo", []byte("OK"))
js.Publish("bar", []byte("OK"))
js.Publish("baz", []byte("OK"))
for i := 0; i < 12; i++ {
js.Publish(fmt.Sprintf("N.%d", i+1), []byte("OK"))
}
checkFor(t, 5*time.Second, 250*time.Millisecond, func() error {
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
if si.State.Msgs != 15 {
return fmt.Errorf("Expected 15 msgs, got state: %+v", si.State)
}
return nil
})
checkWider := func(subj string, numExpected int) {
sub, err := js.SubscribeSync(subj)
require_NoError(t, err)
defer sub.Unsubscribe()
checkSubsPending(t, sub, numExpected)
}
checkWider("*", 3)
checkWider("N.*", 12)
checkWider("*.*", 12)
checkWider("N.>", 12)
checkWider(">", 15)
}
func TestJetStreamMirrorAndSourcesFilteredConsumers(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Origin
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar", "baz.*"},
})
require_NoError(t, err)
// Create Mirror now.
_, err = js.AddStream(&nats.StreamConfig{
Name: "M",
Mirror: &nats.StreamSource{Name: "TEST"},
})
require_NoError(t, 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})
require_NoError(t, err)
}
createConsumer("M", "foo")
createConsumer("M", "bar")
createConsumer("M", "baz.foo")
// 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 Mirror now.
_, err = js.AddStream(&nats.StreamConfig{
Name: "S",
Sources: []*nats.StreamSource{{Name: "O1"}, {Name: "O2"}},
})
require_NoError(t, err)
createConsumer("S", "foo.1")
createConsumer("S", "bar.1")
// Chaining
// Create Mirror now.
_, err = js.AddStream(&nats.StreamConfig{
Name: "M2",
Mirror: &nats.StreamSource{Name: "M"},
})
require_NoError(t, err)
createConsumer("M2", "foo")
createConsumer("M2", "bar")
createConsumer("M2", "baz.foo")
}
func TestJetStreamMirrorBasics(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
createStream := func(cfg *nats.StreamConfig) (*nats.StreamInfo, error) {
return js.AddStream(cfg)
}
createStreamOk := func(cfg *nats.StreamConfig) {
t.Helper()
if _, err := createStream(cfg); err != nil {
t.Fatalf("Expected no error, got %+v", err)
}
}
// Test we get right config errors etc.
cfg := &nats.StreamConfig{
Name: "M1",
Subjects: []string{"foo", "bar", "baz"},
Mirror: &nats.StreamSource{Name: "S1"},
}
_, err := createStream(cfg)
if err == nil || !strings.Contains(err.Error(), "stream mirrors can not") {
t.Fatalf("Expected error, got %+v", err)
}
// Clear subjects.
cfg.Subjects = nil
// Mirrored
scfg := &nats.StreamConfig{
Name: "S1",
Subjects: []string{"foo", "bar", "baz"},
}
// Create mirrored stream
createStreamOk(scfg)
// Now create our mirror stream.
createStreamOk(cfg)
// For now wait for the consumer state to register.
time.Sleep(250 * time.Millisecond)
// Send 100 messages.
for i := 0; i < 100; i++ {
if _, err := js.Publish("foo", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
// Faster timeout since we loop below checking for condition.
js2, err := nc.JetStream(nats.MaxWait(500 * time.Millisecond))
require_NoError(t, err)
checkFor(t, 5*time.Second, 250*time.Millisecond, func() error {
si, err := js2.StreamInfo("M1")
require_NoError(t, err)
if si.State.Msgs != 100 {
return fmt.Errorf("Expected 100 msgs, got state: %+v", si.State)
}
return nil
})
// Purge the mirrored stream.
if err := js.PurgeStream("S1"); err != nil {
t.Fatalf("Unexpected purge error: %v", err)
}
// Send 50 more msgs now.
for i := 0; i < 50; i++ {
if _, err := js.Publish("bar", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
cfg = &nats.StreamConfig{
Name: "M2",
Storage: nats.FileStorage,
Mirror: &nats.StreamSource{Name: "S1"},
}
// Now create our second mirror stream.
createStreamOk(cfg)
checkFor(t, 5*time.Second, 250*time.Millisecond, func() error {
si, err := js2.StreamInfo("M2")
require_NoError(t, err)
if si.State.Msgs != 50 {
return fmt.Errorf("Expected 50 msgs, got state: %+v", si.State)
}
if si.State.FirstSeq != 101 {
return fmt.Errorf("Expected start seq of 101, got state: %+v", si.State)
}
return nil
})
// Send 100 more msgs now. Should be 150 total, 101 first.
for i := 0; i < 100; i++ {
if _, err := js.Publish("baz", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
cfg = &nats.StreamConfig{
Name: "M3",
Mirror: &nats.StreamSource{Name: "S1", OptStartSeq: 150},
}
createStreamOk(cfg)
checkFor(t, 5*time.Second, 250*time.Millisecond, func() error {
si, err := js2.StreamInfo("M3")
require_NoError(t, err)
if si.State.Msgs != 101 {
return fmt.Errorf("Expected 101 msgs, got state: %+v", si.State)
}
if si.State.FirstSeq != 150 {
return fmt.Errorf("Expected start seq of 150, got state: %+v", si.State)
}
return nil
})
// Make sure setting time works ok.
start := time.Now().UTC().Add(-2 * time.Hour)
cfg = &nats.StreamConfig{
Name: "M4",
Mirror: &nats.StreamSource{Name: "S1", OptStartTime: &start},
}
createStreamOk(cfg)
checkFor(t, 5*time.Second, 250*time.Millisecond, func() error {
si, err := js2.StreamInfo("M4")
require_NoError(t, err)
if si.State.Msgs != 150 {
return fmt.Errorf("Expected 150 msgs, got state: %+v", si.State)
}
if si.State.FirstSeq != 101 {
return fmt.Errorf("Expected start seq of 101, got state: %+v", si.State)
}
return nil
})
// Test subject filtering and transformation
createStreamServerStreamConfig := func(cfg *StreamConfig, errToCheck uint16) {
t.Helper()
req, err := json.Marshal(cfg)
require_NoError(t, err)
rm, err := nc.Request(fmt.Sprintf(JSApiStreamCreateT, cfg.Name), req, time.Second)
require_NoError(t, err)
var resp JSApiStreamCreateResponse
if err := json.Unmarshal(rm.Data, &resp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if errToCheck == 0 {
if resp.Error != nil {
t.Fatalf("Unexpected error: %+v", resp.Error)
}
} else {
if resp.Error.ErrCode != errToCheck {
t.Fatalf("Expected error %+v, got: %+v", errToCheck, resp.Error)
}
}
}
// check for errors
createStreamServerStreamConfig(&StreamConfig{
Name: "MBAD",
Storage: FileStorage,
Mirror: &StreamSource{Name: "S1", FilterSubject: "foo", SubjectTransforms: []SubjectTransformConfig{{Source: "foo", Destination: "foo3"}}},
}, ApiErrors[JSMirrorMultipleFiltersNotAllowed].ErrCode)
createStreamServerStreamConfig(&StreamConfig{
Name: "MBAD",
Storage: FileStorage,
Mirror: &StreamSource{Name: "S1", SubjectTransforms: []SubjectTransformConfig{{Source: ".*.", Destination: "foo3"}}},
}, ApiErrors[JSMirrorInvalidSubjectFilter].ErrCode)
createStreamServerStreamConfig(&StreamConfig{
Name: "MBAD",
Storage: FileStorage,
Mirror: &StreamSource{Name: "S1", SubjectTransforms: []SubjectTransformConfig{{Source: "*", Destination: "{{wildcard(2)}}"}}},
}, ApiErrors[JSMirrorInvalidTransformDestination].ErrCode)
createStreamServerStreamConfig(&StreamConfig{
Name: "MBAD",
Storage: FileStorage,
Mirror: &StreamSource{Name: "S1", SubjectTransforms: []SubjectTransformConfig{{Source: "foo", Destination: ""}, {Source: "foo", Destination: "bar"}}},
}, ApiErrors[JSMirrorOverlappingSubjectFilters].ErrCode)
createStreamServerStreamConfig(&StreamConfig{
Name: "M5",
Storage: FileStorage,
Mirror: &StreamSource{Name: "S1", SubjectTransforms: []SubjectTransformConfig{{Source: "foo", Destination: "foo2"}}},
}, 0)
createStreamServerStreamConfig(&StreamConfig{
Name: "M6",
Storage: FileStorage,
Mirror: &StreamSource{Name: "S1", SubjectTransforms: []SubjectTransformConfig{{Source: "bar", Destination: "bar2"}, {Source: "baz", Destination: "baz2"}}},
}, 0)
// Send 100 messages on foo (there should already be 50 messages on bar and 100 on baz in the stream)
for i := 0; i < 100; i++ {
if _, err := js.Publish("foo", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
var f = func(streamName string, subject string, subjectNumMsgs uint64, streamNumMsg uint64, firstSeq uint64, lastSeq uint64) func() error {
return func() error {
si, err := js2.StreamInfo(streamName, &nats.StreamInfoRequest{SubjectsFilter: subject})
require_NoError(t, err)
if ss, ok := si.State.Subjects[subject]; !ok {
return fmt.Errorf("expected messages with the transformed subject %s", subject)
} else {
if ss != subjectNumMsgs {
return fmt.Errorf("expected %d messages on the transformed subject %s but got %d", subjectNumMsgs, subject, ss)
}
}
if si.State.Msgs != streamNumMsg {
return fmt.Errorf("expected %d stream messages, got state: %+v", streamNumMsg, si.State)
}
if si.State.FirstSeq != firstSeq || si.State.LastSeq != lastSeq {
return fmt.Errorf("expected first sequence=%d and last sequence=%d, but got state: %+v", firstSeq, lastSeq, si.State)
}
return nil
}
}
checkFor(t, 10*time.Second, 500*time.Millisecond, f("M5", "foo2", 100, 100, 251, 350))
checkFor(t, 10*time.Second, 500*time.Millisecond, f("M6", "bar2", 50, 150, 101, 250))
checkFor(t, 10*time.Second, 500*time.Millisecond, f("M6", "baz2", 100, 150, 101, 250))
}
func TestJetStreamMirrorStripExpectedHeaders(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Create source and mirror streams.
_, err := js.AddStream(&nats.StreamConfig{
Name: "S",
Subjects: []string{"foo"},
})
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{
Name: "M",
Mirror: &nats.StreamSource{Name: "S"},
})
require_NoError(t, err)
m := nats.NewMsg("foo")
pubAck, err := js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
// Mirror should get message.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
if si, err := js.StreamInfo("M"); err != nil {
return err
} else if si.State.Msgs != 1 {
return fmt.Errorf("expected 1 mirrored msg, got %d", si.State.Msgs)
}
return nil
})
m.Header.Set("Nats-Expected-Stream", "S")
pubAck, err = js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 2)
// Mirror should strip expected headers and store the message.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
if si, err := js.StreamInfo("M"); err != nil {
return err
} else if si.State.Msgs != 2 {
return fmt.Errorf("expected 2 mirrored msgs, got %d", si.State.Msgs)
}
return nil
})
}
func TestJetStreamMirrorUpdatePreventsSubjects(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "ORIGINAL"})
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{Name: "MIRROR", Mirror: &nats.StreamSource{Name: "ORIGINAL"}})
require_NoError(t, err)
_, err = js.UpdateStream(&nats.StreamConfig{Name: "MIRROR", Mirror: &nats.StreamSource{Name: "ORIGINAL"}, Subjects: []string{"x"}})
if err == nil || err.Error() != "nats: stream mirrors can not contain subjects" {
t.Fatalf("Expected to not be able to put subjects on a stream, got: %+v", err)
}
}
func TestJetStreamSourceBasics(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
createStream := func(cfg *StreamConfig) {
t.Helper()
req, err := json.Marshal(cfg)
require_NoError(t, err)
rm, err := nc.Request(fmt.Sprintf(JSApiStreamCreateT, cfg.Name), req, time.Second)
require_NoError(t, err)
var resp JSApiStreamCreateResponse
if err := json.Unmarshal(rm.Data, &resp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if resp.Error != nil {
t.Fatalf("Unexpected error: %+v", resp.Error)
}
}
if _, err := js.AddStream(&nats.StreamConfig{Name: "test", Sources: []*nats.StreamSource{{Name: ""}}}); err.Error() == "source stream name is invalid" {
t.Fatal("Expected a source stream name is invalid error")
}
for _, sname := range []string{"foo", "bar", "baz"} {
if _, err := js.AddStream(&nats.StreamConfig{Name: sname}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
sendBatch := func(subject string, n int) {
for i := 0; i < n; i++ {
if _, err := js.Publish(subject, []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
}
// Populate each one.
sendBatch("foo", 10)
sendBatch("bar", 15)
sendBatch("baz", 25)
cfg := &StreamConfig{
Name: "MS",
Storage: FileStorage,
Sources: []*StreamSource{
{Name: "foo", SubjectTransforms: []SubjectTransformConfig{{Source: ">", Destination: "foo2.>"}}},
{Name: "bar"},
{Name: "baz"},
},
}
createStream(cfg)
// Faster timeout since we loop below checking for condition.
js2, err := nc.JetStream(nats.MaxWait(250 * time.Millisecond))
require_NoError(t, err)
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
si, err := js2.StreamInfo("MS")
require_NoError(t, err)
if si.State.Msgs != 50 {
return fmt.Errorf("Expected 50 msgs, got state: %+v", si.State)
}
return nil
})
ss, err := js.SubscribeSync("foo2.foo", nats.BindStream("MS"))
require_NoError(t, err)
// we must have at least one message on the transformed subject name (ie no timeout)
_, err = ss.NextMsg(time.Millisecond)
require_NoError(t, err)
ss.Drain()
// Test Source Updates
ncfg := &nats.StreamConfig{
Name: "MS",
Sources: []*nats.StreamSource{
// Keep foo, bar, remove baz, add dlc
{Name: "foo"},
{Name: "bar"},
{Name: "dlc"},
},
}
if _, err := js.UpdateStream(ncfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Test optional start times, filtered subjects etc.
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"dlc", "rip", "jnm"}}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sendBatch("dlc", 20)
sendBatch("rip", 20)
sendBatch("dlc", 10)
sendBatch("jnm", 10)
cfg = &StreamConfig{
Name: "FMS",
Storage: FileStorage,
Sources: []*StreamSource{
{Name: "TEST", OptStartSeq: 26},
},
}
createStream(cfg)
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
si, err := js2.StreamInfo("FMS")
require_NoError(t, err)
if si.State.Msgs != 35 {
return fmt.Errorf("Expected 35 msgs, got state: %+v", si.State)
}
return nil
})
// Double check first starting.
m, err := js.GetMsg("FMS", 1)
require_NoError(t, err)
if shdr := m.Header.Get(JSStreamSource); shdr == _EMPTY_ {
t.Fatalf("Expected a header, got none")
} else if _, _, sseq := streamAndSeq(shdr); sseq != 26 {
t.Fatalf("Expected header sequence of 26, got %d", sseq)
}
// Test Filters
cfg = &StreamConfig{
Name: "FMS2",
Storage: FileStorage,
Sources: []*StreamSource{
{Name: "TEST", OptStartSeq: 11, SubjectTransforms: []SubjectTransformConfig{{Source: "dlc", Destination: "dlc2"}}},
},
}
createStream(cfg)
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
si, err := js2.StreamInfo("FMS2")
require_NoError(t, err)
if si.State.Msgs != 20 {
return fmt.Errorf("Expected 20 msgs, got state: %+v", si.State)
}
return nil
})
// Double check first starting.
if m, err = js.GetMsg("FMS2", 1); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if shdr := m.Header.Get(JSStreamSource); shdr == _EMPTY_ {
t.Fatalf("Expected a header, got none")
} else if _, _, sseq := streamAndSeq(shdr); sseq != 11 {
t.Fatalf("Expected header sequence of 11, got %d", sseq)
}
if m.Subject != "dlc2" {
t.Fatalf("Expected transformed subject dlc2, but got %s instead", m.Subject)
}
// Test Filters
cfg = &StreamConfig{
Name: "FMS3",
Storage: FileStorage,
Sources: []*StreamSource{
{Name: "TEST", SubjectTransforms: []SubjectTransformConfig{{Source: "dlc", Destination: "dlc2"}, {Source: "rip", Destination: ""}}},
},
}
createStream(cfg)
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
si, err := js2.StreamInfo("FMS3")
require_NoError(t, err)
if si.State.Msgs != 50 {
return fmt.Errorf("Expected 50 msgs, got state: %+v", si.State)
}
return nil
})
// Double check first message
if m, err = js.GetMsg("FMS3", 1); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if shdr := m.Header.Get(JSStreamSource); shdr == _EMPTY_ {
t.Fatalf("Expected a header, got none")
} else if m.Subject != "dlc2" {
t.Fatalf("Expected subject 'dlc2' and got %s", m.Subject)
}
// Double check first message with the other subject
if m, err = js.GetMsg("FMS3", 21); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if shdr := m.Header.Get(JSStreamSource); shdr == _EMPTY_ {
t.Fatalf("Expected a header, got none")
} else if m.Subject != "rip" {
t.Fatalf("Expected subject 'rip' and got %s", m.Subject)
}
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
si, err := js2.StreamInfo("FMS3")
require_NoError(t, err)
if si.State.Subjects["jnm"] != 0 {
return fmt.Errorf("Unexpected messages from the source found")
}
return nil
})
// pre 2.10 backwards compatibility
transformConfig := nats.SubjectTransformConfig{Source: "B.*", Destination: "A.{{Wildcard(1)}}"}
aConfig := nats.StreamConfig{Name: "A", Subjects: []string{"B.*"}, SubjectTransform: &transformConfig}
if _, err := js.AddStream(&aConfig); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sendBatch("B.A", 1)
sendBatch("B.B", 1)
bConfig := nats.StreamConfig{Name: "B", Subjects: []string{"A.*"}}
if _, err := js.AddStream(&bConfig); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// fake a message that would have been sourced with pre 2.10
msg := nats.NewMsg("A.A")
// pre 2.10 header format just stream name and sequence number
msg.Header.Set(JSStreamSource, "A 1")
msg.Data = []byte("OK")
if _, err := js.PublishMsg(msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
bConfig.Sources = []*nats.StreamSource{{Name: "A"}}
if _, err := js.UpdateStream(&bConfig); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
si, err := js2.StreamInfo("B")
require_NoError(t, err)
if si.State.Msgs != 2 {
return fmt.Errorf("Expected 2 msgs, got state: %+v", si.State)
}
return nil
})
}
func TestJetStreamSourceWorkingQueueWithLimit(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "test", Subjects: []string{"test"}})
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{Name: "wq", MaxMsgs: 100, Discard: nats.DiscardNew, Retention: nats.WorkQueuePolicy,
Sources: []*nats.StreamSource{{Name: "test"}}})
require_NoError(t, err)
sendBatch := func(subject string, n int) {
for i := 0; i < n; i++ {
_, err = js.Publish(subject, []byte("OK"))
require_NoError(t, err)
}
}
// Populate each one.
sendBatch("test", 300)
checkFor(t, 3*time.Second, 250*time.Millisecond, func() error {
si, err := js.StreamInfo("wq")
require_NoError(t, err)
if si.State.Msgs != 100 {
return fmt.Errorf("Expected 100 msgs, got state: %+v", si.State)
}
return nil
})
_, err = js.AddConsumer("wq", &nats.ConsumerConfig{Durable: "wqc", FilterSubject: "test", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
ss, err := js.PullSubscribe("test", "wqc", nats.Bind("wq", "wqc"))
require_NoError(t, err)
// we must have at least one message on the transformed subject name (ie no timeout)
f := func(done chan bool) {
for i := 0; i < 300; i++ {
m, err := ss.Fetch(1, nats.MaxWait(3*time.Second))
require_NoError(t, err)
time.Sleep(11 * time.Millisecond)
err = m[0].Ack()
require_NoError(t, err)
}
done <- true
}
var doneChan = make(chan bool)
go f(doneChan)
checkFor(t, 6*time.Second, 100*time.Millisecond, func() error {
si, err := js.StreamInfo("wq")
require_NoError(t, err)
if si.State.Msgs > 0 && si.State.Msgs <= 100 {
return fmt.Errorf("Expected 0 msgs, got: %d", si.State.Msgs)
} else if si.State.Msgs > 100 {
t.Fatalf("Got more than our 100 message limit: %+v", si.State)
}
return nil
})
select {
case <-doneChan:
ss.Drain()
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
}
func TestJetStreamStreamSourceFromKV(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API reuqests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Create a kv store
kv, err := js.CreateKeyValue(&nats.KeyValueConfig{Bucket: "test"})
require_NoError(t, err)
// Create a stream with a source from the kv store
_, err = js.AddStream(&nats.StreamConfig{Name: "test", Retention: nats.InterestPolicy, Sources: []*nats.StreamSource{{Name: "KV_" + kv.Bucket()}}})
require_NoError(t, err)
// Create a interested consumer
_, err = js.AddConsumer("test", &nats.ConsumerConfig{Durable: "durable", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
ss, err := js.PullSubscribe("", "", nats.Bind("test", "durable"))
require_NoError(t, err)
rev1, err := kv.Create("key", []byte("value1"))
require_NoError(t, err)
m, err := ss.Fetch(1, nats.MaxWait(2*time.Second))
require_NoError(t, err)
require_NoError(t, m[0].Ack())
if string(m[0].Data) != "value1" {
t.Fatalf("Expected value1, got %s", m[0].Data)
}
rev2, err := kv.Update("key", []byte("value2"), rev1)
require_NoError(t, err)
_, err = kv.Update("key", []byte("value3"), rev2)
require_NoError(t, err)
m, err = ss.Fetch(1, nats.MaxWait(500*time.Millisecond))
require_NoError(t, err)
require_NoError(t, m[0].Ack())
if string(m[0].Data) != "value2" {
t.Fatalf("Expected value2, got %s", m[0].Data)
}
m, err = ss.Fetch(1, nats.MaxWait(500*time.Millisecond))
require_NoError(t, err)
require_NoError(t, m[0].Ack())
if string(m[0].Data) != "value3" {
t.Fatalf("Expected value3, got %s", m[0].Data)
}
}
func TestJetStreamInputTransform(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
createStream := func(cfg *StreamConfig) {
t.Helper()
req, err := json.Marshal(cfg)
require_NoError(t, err)
rm, err := nc.Request(fmt.Sprintf(JSApiStreamCreateT, cfg.Name), req, time.Second)
require_NoError(t, err)
var resp JSApiStreamCreateResponse
if err := json.Unmarshal(rm.Data, &resp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if resp.Error != nil {
t.Fatalf("Unexpected error: %+v", resp.Error)
}
}
createStream(&StreamConfig{Name: "T1", Subjects: []string{"foo"}, SubjectTransform: &SubjectTransformConfig{Source: ">", Destination: "transformed.>"}, Storage: MemoryStorage})
// publish a message
if _, err := js.Publish("foo", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
m, err := js.GetMsg("T1", 1)
require_NoError(t, err)
if m.Subject != "transformed.foo" {
t.Fatalf("Expected message subject transformed.foo, got %s", m.Subject)
}
}
func TestJetStreamOperatorAccounts(t *testing.T) {
s, _ := RunServerWithConfig("./configs/js-op.conf")
if config := s.JetStreamConfig(); config != nil {
defer removeDir(t, config.StoreDir)
}
defer s.Shutdown()
nc, js := jsClientConnect(t, s, nats.UserCredentials("./configs/one.creds"))
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{Name: "TEST"}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
toSend := 100
for i := 0; i < toSend; i++ {
if _, err := js.Publish("TEST", []byte("OK")); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
// Close our user for account one.
nc.Close()
// Restart the server.
s.Shutdown()
s, _ = RunServerWithConfig("./configs/js-op.conf")
defer s.Shutdown()
jsz, err := s.Jsz(nil)
require_NoError(t, err)
if jsz.Streams != 1 {
t.Fatalf("Expected jsz to report our stream on restart")
}
if jsz.Messages != uint64(toSend) {
t.Fatalf("Expected jsz to report our %d messages on restart, got %d", toSend, jsz.Messages)
}
}
func TestJetStreamServerDomainBadConfig(t *testing.T) {
shouldFail := func(domain string) {
t.Helper()
opts := DefaultTestOptions
opts.JetStreamDomain = domain
if err := validateOptions(&opts); err == nil || !strings.Contains(err.Error(), "invalid domain name") {
t.Fatalf("Expected bad domain error, got %v", err)
}
}
shouldFail("HU..B")
shouldFail("HU B")
shouldFail(" ")
shouldFail("\t")
shouldFail("CORE.")
shouldFail(".CORE")
shouldFail("C.*.O. RE")
shouldFail("C.ORE")
}
func TestJetStreamServerDomainConfig(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {domain: "HUB", store_dir: %q}
`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
if !s.JetStreamEnabled() {
t.Fatalf("Expected JetStream to be enabled")
}
config := s.JetStreamConfig()
if config.Domain != "HUB" {
t.Fatalf("Expected %q as domain name, got %q", "HUB", config.Domain)
}
}
func TestJetStreamServerDomainConfigButDisabled(t *testing.T) {
conf := createConfFile(t, []byte(`
listen: 127.0.0.1:-1
jetstream: {domain: "HUB", enabled: false}
`))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
if s.JetStreamEnabled() {
t.Fatalf("Expected JetStream NOT to be enabled")
}
opts := s.getOpts()
if opts.JetStreamDomain != "HUB" {
t.Fatalf("Expected %q as opts domain name, got %q", "HUB", opts.JetStreamDomain)
}
}
func TestJetStreamDomainInPubAck(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {domain: "HUB", store_dir: %q}
`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Storage: nats.MemoryStorage,
Subjects: []string{"foo"},
}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Check by hand for now til it makes its way into Go client.
am, err := nc.Request("foo", nil, time.Second)
require_NoError(t, err)
var pa PubAck
json.Unmarshal(am.Data, &pa)
if pa.Domain != "HUB" {
t.Fatalf("Expected PubAck to have domain of %q, got %q", "HUB", pa.Domain)
}
}
// Issue #2213
func TestJetStreamDirectConsumersBeingReported(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{
Name: "S",
Sources: []*nats.StreamSource{{
Name: "TEST",
}},
})
require_NoError(t, err)
if _, err = js.Publish("foo", nil); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
si, err := js.StreamInfo("S")
if err != nil {
return fmt.Errorf("Could not get stream info: %v", err)
}
if si.State.Msgs != 1 {
return fmt.Errorf("Expected 1 msg, got state: %+v", si.State)
}
return nil
})
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
// Direct consumers should not be reported
if si.State.Consumers != 0 {
t.Fatalf("Did not expect any consumers, got %d", si.State.Consumers)
}
// Now check for consumer in consumer names list.
var names []string
for name := range js.ConsumerNames("TEST") {
names = append(names, name)
}
if len(names) != 0 {
t.Fatalf("Expected no consumers but got %+v", names)
}
// Now check detailed list.
var cis []*nats.ConsumerInfo
for ci := range js.ConsumersInfo("TEST") {
cis = append(cis, ci)
}
if len(cis) != 0 {
t.Fatalf("Expected no consumers but got %+v", cis)
}
}
// https://github.com/nats-io/nats-server/issues/2290
func TestJetStreamTemplatedErrorsBug(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
_, err = js.PullSubscribe("foo", "")
if err != nil && strings.Contains(err.Error(), "{err}") {
t.Fatalf("Error is not filled in: %v", err)
}
}
func TestJetStreamServerEncryption(t *testing.T) {
cases := []struct {
name string
cstr string
cipher StoreCipher
}{
{"Default", _EMPTY_, ChaCha},
{"ChaCha", ", cipher: chacha", ChaCha},
{"AES", ", cipher: aes", AES},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
tmpl := `
server_name: S22
listen: 127.0.0.1:-1
jetstream: {key: $JS_KEY, store_dir: '%s' %s}
`
storeDir := t.TempDir()
conf := createConfFile(t, []byte(fmt.Sprintf(tmpl, storeDir, c.cstr)))
os.Setenv("JS_KEY", "s3cr3t!!")
defer os.Unsetenv("JS_KEY")
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
config := s.JetStreamConfig()
if config == nil {
t.Fatalf("Expected config but got none")
}
defer removeDir(t, config.StoreDir)
// Client based API
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar", "baz"},
}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
msg := []byte("ENCRYPTED PAYLOAD!!")
sendMsg := func(subj string) {
t.Helper()
if _, err := js.Publish(subj, msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
// Send 10 msgs
for i := 0; i < 10; i++ {
sendMsg("foo")
}
// Now create a consumer.
sub, err := js.PullSubscribe("foo", "dlc")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for i, m := range fetchMsgs(t, sub, 10, 5*time.Second) {
if i < 5 {
m.AckSync()
}
}
// Grab our state to compare after restart.
si, _ := js.StreamInfo("TEST")
ci, _ := js.ConsumerInfo("TEST", "dlc")
// Quick check to make sure everything not just plaintext still.
sdir := filepath.Join(config.StoreDir, "$G", "streams", "TEST")
// Make sure we can not find any plaintext strings in the target file.
checkFor := func(fn string, strs ...string) {
t.Helper()
data, err := os.ReadFile(fn)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for _, str := range strs {
if bytes.Contains(data, []byte(str)) {
t.Fatalf("Found %q in body of file contents", str)
}
}
}
checkKeyFile := func(fn string) {
t.Helper()
if _, err := os.Stat(fn); err != nil {
t.Fatalf("Expected a key file at %q", fn)
}
}
// Check stream meta.
checkEncrypted := func() {
t.Helper()
checkKeyFile(filepath.Join(sdir, JetStreamMetaFileKey))
checkFor(filepath.Join(sdir, JetStreamMetaFile), "TEST", "foo", "bar", "baz", "max_msgs", "max_bytes")
// Check a message block.
checkKeyFile(filepath.Join(sdir, "msgs", "1.key"))
checkFor(filepath.Join(sdir, "msgs", "1.blk"), "ENCRYPTED PAYLOAD!!", "foo", "bar", "baz")
// Check consumer meta and state.
checkKeyFile(filepath.Join(sdir, "obs", "dlc", JetStreamMetaFileKey))
checkFor(filepath.Join(sdir, "obs", "dlc", JetStreamMetaFile), "TEST", "dlc", "foo", "bar", "baz", "max_msgs", "ack_policy")
// Load and see if we can parse the consumer state.
state, err := os.ReadFile(filepath.Join(sdir, "obs", "dlc", "o.dat"))
require_NoError(t, err)
if _, err := decodeConsumerState(state); err == nil {
t.Fatalf("Expected decoding consumer state to fail")
}
}
// Stop current
s.Shutdown()
checkEncrypted()
// Restart.
s, _ = RunServerWithConfig(conf)
defer s.Shutdown()
// Connect again.
nc, js = jsClientConnect(t, s)
defer nc.Close()
si2, err := js.StreamInfo("TEST")
require_NoError(t, err)
if !reflect.DeepEqual(si, si2) {
t.Fatalf("Stream infos did not match\n%+v\nvs\n%+v", si, si2)
}
ci2, _ := js.ConsumerInfo("TEST", "dlc")
// Consumer create times can be slightly off after restore from disk.
now := time.Now()
ci.Created, ci2.Created = now, now
ci.Delivered.Last, ci2.Delivered.Last = nil, nil
ci.AckFloor.Last, ci2.AckFloor.Last = nil, nil
// Also clusters will be different.
ci.Cluster, ci2.Cluster = nil, nil
if !reflect.DeepEqual(ci, ci2) {
t.Fatalf("Consumer infos did not match\n%+v\nvs\n%+v", ci, ci2)
}
// Send 10 more msgs
for i := 0; i < 10; i++ {
sendMsg("foo")
}
if si, err = js.StreamInfo("TEST"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si.State.Msgs != 20 {
t.Fatalf("Expected 20 msgs total, got %d", si.State.Msgs)
}
// Now test snapshots etc.
acc := s.GlobalAccount()
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
scfg := mset.config()
sr, err := mset.snapshot(5*time.Second, false, true)
if err != nil {
t.Fatalf("Error getting snapshot: %v", err)
}
snapshot, err := io.ReadAll(sr.Reader)
if err != nil {
t.Fatalf("Error reading snapshot")
}
// Run new server w/o encryption. Make sure we can restore properly (meaning encryption was stripped etc).
ns := RunBasicJetStreamServer(t)
defer ns.Shutdown()
nacc := ns.GlobalAccount()
r := bytes.NewReader(snapshot)
mset, err = nacc.RestoreStream(&scfg, r)
require_NoError(t, err)
ss := mset.store.State()
if ss.Msgs != si.State.Msgs || ss.FirstSeq != si.State.FirstSeq || ss.LastSeq != si.State.LastSeq {
t.Fatalf("Stream states do not match: %+v vs %+v", ss, si.State)
}
// Now restore to our encrypted server as well.
if err := js.DeleteStream("TEST"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
acc = s.GlobalAccount()
r.Reset(snapshot)
mset, err = acc.RestoreStream(&scfg, r)
require_NoError(t, err)
ss = mset.store.State()
if ss.Msgs != si.State.Msgs || ss.FirstSeq != si.State.FirstSeq || ss.LastSeq != si.State.LastSeq {
t.Fatalf("Stream states do not match: %+v vs %+v", ss, si.State)
}
// Check that all is encrypted like above since we know we need to convert since snapshots always plaintext.
checkEncrypted()
})
}
}
func TestJetStreamServerEncryptionServerRestarts(t *testing.T) {
cases := []struct {
name string
cstr string
cipher StoreCipher
}{
{"Default", _EMPTY_, ChaCha},
{"ChaCha", ", cipher: chacha", ChaCha},
{"AES", ", cipher: aes", AES},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
tmpl := `
server_name: S22
listen: 127.0.0.1:-1
jetstream: {key: $JS_KEY, store_dir: '%s' %s}
`
storeDir := t.TempDir()
conf := createConfFile(t, []byte(fmt.Sprintf(tmpl, storeDir, c.cstr)))
os.Setenv("JS_KEY", "s3cr3t!!")
defer os.Unsetenv("JS_KEY")
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
config := s.JetStreamConfig()
if config == nil {
t.Fatalf("Expected config but got none")
}
defer removeDir(t, config.StoreDir)
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Add stream.
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: nats.FileStorage,
}
_, err := js.AddStream(cfg)
require_NoError(t, err)
// Restart to invalidate any in-memory state that adding the stream created.
s.Shutdown()
s, _ = RunServerWithConfig(conf)
defer s.Shutdown()
nc.Close()
nc, js = jsClientConnect(t, s)
defer nc.Close()
msg := []byte("ENCRYPTED PAYLOAD!!")
_, err = js.Publish("foo", msg)
require_NoError(t, err)
// Restart to invalidate any in-memory state that the publish initialized.
s.Shutdown()
s, _ = RunServerWithConfig(conf)
defer s.Shutdown()
nc.Close()
nc, js = jsClientConnect(t, s)
defer nc.Close()
// Should still be able to get the data.
sub, err := js.SubscribeSync("foo")
require_NoError(t, err)
defer sub.Drain()
m, err := sub.NextMsg(time.Second)
require_NoError(t, err)
meta, err := m.Metadata()
require_NoError(t, err)
require_Equal(t, meta.Sequence.Stream, 1)
})
}
}
func TestJetStreamDeliverLastPerSubject(t *testing.T) {
for _, st := range []StorageType{FileStorage, MemoryStorage} {
t.Run(st.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := StreamConfig{
Name: "KV",
Subjects: []string{"kv.>"},
Storage: st,
MaxMsgsPer: 5,
}
req, err := json.Marshal(cfg)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Do manually for now.
nc.Request(fmt.Sprintf(JSApiStreamCreateT, cfg.Name), req, time.Second)
si, err := js.StreamInfo("KV")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if si == nil || si.Config.Name != "KV" {
t.Fatalf("StreamInfo is not correct %+v", si)
}
// Interleave them on purpose.
for i := 1; i <= 11; i++ {
msg := []byte(fmt.Sprintf("%d", i))
js.PublishAsync("kv.b1.foo", msg)
js.PublishAsync("kv.b2.foo", msg)
js.PublishAsync("kv.b1.bar", msg)
js.PublishAsync("kv.b2.bar", msg)
js.PublishAsync("kv.b1.baz", msg)
js.PublishAsync("kv.b2.baz", msg)
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(2 * time.Second):
t.Fatalf("Did not receive completion signal")
}
// Do quick check that config needs FilteredSubjects otherwise bad config.
badReq := CreateConsumerRequest{
Stream: "KV",
Config: ConsumerConfig{
DeliverSubject: "b",
DeliverPolicy: DeliverLastPerSubject,
},
}
req, err = json.Marshal(badReq)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
resp, err := nc.Request(fmt.Sprintf(JSApiConsumerCreateT, "KV"), 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 || !strings.Contains(ccResp.Error.Description, "filter subject is not set") {
t.Fatalf("Expected an error, got none")
}
// Now let's consume these via last per subject.
obsReq := CreateConsumerRequest{
Stream: "KV",
Config: ConsumerConfig{
DeliverSubject: "d",
DeliverPolicy: DeliverLastPerSubject,
FilterSubject: "kv.b1.*",
},
}
req, err = json.Marshal(obsReq)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
resp, err = nc.Request(fmt.Sprintf(JSApiConsumerCreateT, "KV"), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ccResp.Error = nil
if err = json.Unmarshal(resp.Data, &ccResp); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
sub, _ := nc.SubscribeSync("d")
defer sub.Unsubscribe()
// Helper to check messages are correct.
checkNext := func(subject string, sseq uint64, v string) {
t.Helper()
m, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("Error receiving message: %v", err)
}
if m.Subject != subject {
t.Fatalf("Expected subject %q but got %q", subject, m.Subject)
}
meta, err := m.Metadata()
if err != nil {
t.Fatalf("didn't get metadata: %s", err)
}
if meta.Sequence.Stream != sseq {
t.Fatalf("Expected stream seq %d but got %d", sseq, meta.Sequence.Stream)
}
if string(m.Data) != v {
t.Fatalf("Expected data of %q but got %q", v, m.Data)
}
}
checkSubsPending(t, sub, 3)
// Now make sure they are what we expect.
checkNext("kv.b1.foo", 61, "11")
checkNext("kv.b1.bar", 63, "11")
checkNext("kv.b1.baz", 65, "11")
msg := []byte(fmt.Sprintf("%d", 22))
js.Publish("kv.b1.bar", msg)
js.Publish("kv.b2.foo", msg) // Not filtered through..
checkSubsPending(t, sub, 1)
checkNext("kv.b1.bar", 67, "22")
})
}
}
func TestJetStreamDeliverLastPerSubjectNumPending(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
if _, err := js.AddStream(&nats.StreamConfig{
Name: "KV",
Subjects: []string{"KV.>"},
MaxMsgsPerSubject: 5,
Replicas: 1,
}); err != nil {
t.Fatalf("Error adding stream: %v", err)
}
for i := 0; i < 5; i++ {
msg := []byte(fmt.Sprintf("msg%d", i))
js.Publish("KV.foo", msg)
js.Publish("KV.bar", msg)
js.Publish("KV.baz", msg)
js.Publish("KV.bat", msg)
}
// Delete some messages
js.DeleteMsg("KV", 2)
js.DeleteMsg("KV", 5)
ci, err := js.AddConsumer("KV", &nats.ConsumerConfig{
DeliverSubject: nats.NewInbox(),
AckPolicy: nats.AckExplicitPolicy,
DeliverPolicy: nats.DeliverLastPerSubjectPolicy,
FilterSubject: "KV.>",
})
if err != nil {
t.Fatalf("Error adding consumer: %v", err)
}
if ci.NumPending != 4 {
t.Fatalf("Expected 4 pending msgs, got %v", ci.NumPending)
}
}
// Issue #2392
func TestJetStreamPurgeEffectsConsumerDelivery(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
})
require_NoError(t, err)
js.Publish("foo.a", []byte("show once"))
sub, err := js.SubscribeSync("foo.*", nats.AckWait(250*time.Millisecond), nats.DeliverAll(), nats.AckExplicit())
require_NoError(t, err)
defer sub.Unsubscribe()
checkSubsPending(t, sub, 1)
// Do not ack.
if _, err := sub.NextMsg(time.Second); err != nil {
t.Fatalf("Error receiving message: %v", err)
}
// Now purge stream.
if err := js.PurgeStream("TEST"); err != nil {
t.Fatalf("Unexpected purge error: %v", err)
}
js.Publish("foo.b", []byte("show twice?"))
// Do not ack again, should show back up.
if _, err := sub.NextMsg(time.Second); err != nil {
t.Fatalf("Error receiving message: %v", err)
}
// Make sure we get it back.
if _, err := sub.NextMsg(time.Second); err != nil {
t.Fatalf("Error receiving message: %v", err)
}
}
// Issue #2403
func TestJetStreamExpireCausesDeadlock(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
Storage: nats.MemoryStorage,
MaxMsgs: 10,
Retention: nats.InterestPolicy,
})
require_NoError(t, err)
sub, err := js.SubscribeSync("foo.bar")
require_NoError(t, err)
defer sub.Unsubscribe()
// Publish from two connections to get the write lock request wedged in between
// having the RLock and wanting it again deeper in the stack.
nc2, js2 := jsClientConnect(t, s)
defer nc2.Close()
for i := 0; i < 1000; i++ {
js.PublishAsync("foo.bar", []byte("HELLO"))
js2.PublishAsync("foo.bar", []byte("HELLO"))
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
// If we deadlocked then we will not be able to get stream info.
if _, err := js.StreamInfo("TEST"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
// Issue #2420
func TestJetStreamDefaultMaxMsgsPer(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
si, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
Storage: nats.MemoryStorage,
MaxMsgs: 10,
})
require_NoError(t, err)
if si.Config.MaxMsgsPerSubject != -1 {
t.Fatalf("Expected default of -1, got %d", si.Config.MaxMsgsPerSubject)
}
}
// Got a report of streams that expire all messages while the server is down report errors when clients reconnect
// and try to send new messages.
func TestJetStreamExpireAllWhileServerDown(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
MaxAge: 250 * time.Millisecond,
}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
toSend := 10_000
for i := 0; i < toSend; i++ {
js.PublishAsync("TEST", []byte("OK"))
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(time.Second):
t.Fatalf("Did not receive completion signal")
}
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
time.Sleep(300 * time.Millisecond)
// Restart after expire.
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
if si, err := js.StreamInfo("TEST"); err != nil || si.State.Msgs != 0 {
t.Fatalf("Unexpected stream info state: %+v", si)
}
for i := 0; i < 10; i++ {
if _, err := js.Publish("TEST", []byte("OK")); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
if si, err := js.StreamInfo("TEST"); err != nil || si.State.Msgs != 10 {
t.Fatalf("Unexpected stream info state: %+v", si)
}
}
func TestJetStreamLongStreamNamesAndPubAck(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: strings.Repeat("ZABC", 256/4)[:255],
Subjects: []string{"foo"},
}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
js.Publish("foo", []byte("HELLO"))
}
func TestJetStreamPerSubjectPending(t *testing.T) {
for _, st := range []nats.StorageType{nats.FileStorage, nats.MemoryStorage} {
t.Run(st.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "KV_X",
Subjects: []string{"$KV.X.>"},
MaxMsgsPerSubject: 5,
Storage: st,
})
if err != nil {
t.Fatalf("add stream failed: %s", err)
}
// the message we will care for
_, err = js.Publish("$KV.X.x.y.z", []byte("hello world"))
if err != nil {
t.Fatalf("publish failed: %s", err)
}
// make sure there's some unrelated message after
_, err = js.Publish("$KV.X.1", []byte("hello world"))
if err != nil {
t.Fatalf("publish failed: %s", err)
}
// we expect the wildcard filter subject to match only the one message and so pending will be 0
sub, err := js.SubscribeSync("$KV.X.x.>", nats.DeliverLastPerSubject())
if err != nil {
t.Fatalf("subscribe failed: %s", err)
}
msg, err := sub.NextMsg(time.Second)
if err != nil {
t.Fatalf("next failed: %s", err)
}
meta, err := msg.Metadata()
if err != nil {
t.Fatalf("meta failed: %s", err)
}
// with DeliverLastPerSubject set this is never 0, but without setting that its 0 correctly
if meta.NumPending != 0 {
t.Fatalf("expected numpending 0 got %d", meta.NumPending)
}
})
}
}
func TestJetStreamPublishExpectNoMsg(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "KV",
Subjects: []string{"KV.>"},
MaxMsgsPerSubject: 5,
})
if err != nil {
t.Fatalf("add stream failed: %s", err)
}
if _, err = js.Publish("KV.22", []byte("hello world")); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// This should succeed.
m := nats.NewMsg("KV.33")
m.Header.Set(JSExpectedLastSubjSeq, "0")
if _, err := js.PublishMsg(m); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// This should fail.
m = nats.NewMsg("KV.22")
m.Header.Set(JSExpectedLastSubjSeq, "0")
if _, err := js.PublishMsg(m); err == nil {
t.Fatalf("Expected error: %v", err)
}
if err := js.PurgeStream("KV"); err != nil {
t.Fatalf("Unexpected purge error: %v", err)
}
// This should succeed now.
if _, err := js.PublishMsg(m); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}
func TestJetStreamNegativeDupeWindow(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// we incorrectly set MaxAge to -1 which then as a side effect sets dupe window to -1 which should fail
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: nil,
Retention: nats.WorkQueuePolicy,
MaxConsumers: 1,
MaxMsgs: -1,
MaxBytes: -1,
Discard: nats.DiscardNew,
MaxAge: 0,
Duplicates: -1,
MaxMsgsPerSubject: -1,
MaxMsgSize: -1,
Storage: nats.FileStorage,
Replicas: 1,
NoAck: false,
})
if err == nil || err.Error() != "nats: duplicates window can not be negative" {
t.Fatalf("Expected dupe window error got: %v", err)
}
}
// Issue #2551
func TestJetStreamMirroredConsumerFailAfterRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "S1",
Storage: nats.FileStorage,
Subjects: []string{"foo", "bar", "baz"},
})
if err != nil {
t.Fatalf("create failed: %s", err)
}
_, err = js.AddStream(&nats.StreamConfig{
Name: "M1",
Storage: nats.FileStorage,
Mirror: &nats.StreamSource{Name: "S1"},
})
if err != nil {
t.Fatalf("create failed: %s", err)
}
_, err = js.AddConsumer("M1", &nats.ConsumerConfig{
Durable: "C1",
FilterSubject: ">",
AckPolicy: nats.AckExplicitPolicy,
})
if err != nil {
t.Fatalf("consumer create failed: %s", err)
}
// Stop current
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
s.WaitForShutdown()
// Restart.
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
_, err = js.StreamInfo("M1")
if err != nil {
t.Fatalf("%s did not exist after start: %s", "M1", err)
}
_, err = js.ConsumerInfo("M1", "C1")
if err != nil {
t.Fatalf("C1 did not exist after start: %s", err)
}
}
func TestJetStreamDisabledLimitsEnforcementJWT(t *testing.T) {
updateJwt := func(url string, akp nkeys.KeyPair, pubKey string, jwt string) {
t.Helper()
c := natsConnect(t, url, createUserCreds(t, nil, akp))
defer c.Close()
if msg, err := c.Request(fmt.Sprintf(accUpdateEventSubjNew, pubKey), []byte(jwt), time.Second); err != nil {
t.Fatal("error not expected in this test", err)
} else {
content := make(map[string]any)
if err := json.Unmarshal(msg.Data, &content); err != nil {
t.Fatalf("%v", err)
} else if _, ok := content["data"]; !ok {
t.Fatalf("did not get an ok response got: %v", content)
}
}
}
// create system account
sysKp, _ := nkeys.CreateAccount()
sysPub, _ := sysKp.PublicKey()
// limits to apply and check
limits1 := jwt.JetStreamLimits{MemoryStorage: 1024, DiskStorage: 0, Streams: 1, Consumer: 2}
akp, _ := nkeys.CreateAccount()
aPub, _ := akp.PublicKey()
claim := jwt.NewAccountClaims(aPub)
claim.Limits.JetStreamLimits = limits1
aJwt1, err := claim.Encode(oKp)
require_NoError(t, err)
dir := t.TempDir()
storeDir1 := t.TempDir()
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: -1
jetstream: {store_dir: '%s'}
operator: %s
resolver: {
type: full
dir: '%s'
}
system_account: %s
`, storeDir1, ojwt, dir, sysPub)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
updateJwt(s.ClientURL(), sysKp, aPub, aJwt1)
c := natsConnect(t, s.ClientURL(), createUserCreds(t, nil, akp), nats.ReconnectWait(200*time.Millisecond))
defer c.Close()
// keep using the same connection
js, err := c.JetStream()
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{
Name: "disk",
Storage: nats.FileStorage,
Subjects: []string{"disk"},
})
require_Error(t, err)
}
func TestJetStreamDisabledLimitsEnforcement(t *testing.T) {
storeDir1 := t.TempDir()
conf1 := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 256MB, max_file_store: 2GB, store_dir: '%s'}
accounts {
one {
jetstream: {
mem: 1024
disk: 0
streams: 1
consumers: 2
}
users [{user: one, password: password}]
}
}
no_auth_user: one
`, storeDir1)))
s, _ := RunServerWithConfig(conf1)
defer s.Shutdown()
c := natsConnect(t, s.ClientURL())
defer c.Close()
// keep using the same connection
js, err := c.JetStream()
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{
Name: "disk",
Storage: nats.FileStorage,
Subjects: []string{"disk"},
})
require_Error(t, err)
}
// Issue #2607
func TestJetStreamPurgeAndFilteredConsumers(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "S", Subjects: []string{"FOO.*"}})
require_NoError(t, err)
for i := 0; i < 10; i++ {
_, err = js.Publish("FOO.adam", []byte("M"))
require_NoError(t, err)
_, err = js.Publish("FOO.eve", []byte("F"))
require_NoError(t, err)
}
ci, err := js.AddConsumer("S", &nats.ConsumerConfig{
Durable: "adam",
AckPolicy: nats.AckExplicitPolicy,
FilterSubject: "FOO.adam",
})
require_NoError(t, err)
if ci.NumPending != 10 {
t.Fatalf("Expected NumPending to be 10, got %d", ci.NumPending)
}
ci, err = js.AddConsumer("S", &nats.ConsumerConfig{
Durable: "eve",
AckPolicy: nats.AckExplicitPolicy,
FilterSubject: "FOO.eve",
})
require_NoError(t, err)
if ci.NumPending != 10 {
t.Fatalf("Expected NumPending to be 10, got %d", ci.NumPending)
}
// Also check unfiltered with interleaving messages.
_, err = js.AddConsumer("S", &nats.ConsumerConfig{
Durable: "all",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
// Now purge only adam.
jr, _ := json.Marshal(&JSApiStreamPurgeRequest{Subject: "FOO.adam"})
_, err = nc.Request(fmt.Sprintf(JSApiStreamPurgeT, "S"), jr, time.Second)
require_NoError(t, err)
si, err := js.StreamInfo("S")
require_NoError(t, err)
if si.State.Msgs != 10 {
t.Fatalf("Expected 10 messages after purge, got %d", si.State.Msgs)
}
ci, err = js.ConsumerInfo("S", "eve")
require_NoError(t, err)
if ci.NumPending != 10 {
t.Fatalf("Expected NumPending to be 10, got %d", ci.NumPending)
}
ci, err = js.ConsumerInfo("S", "adam")
require_NoError(t, err)
if ci.NumPending != 0 {
t.Fatalf("Expected NumPending to be 0, got %d", ci.NumPending)
}
if ci.AckFloor.Stream != 20 {
t.Fatalf("Expected AckFloor for stream to be 20, got %d", ci.AckFloor.Stream)
}
ci, err = js.ConsumerInfo("S", "all")
require_NoError(t, err)
if ci.NumPending != 10 {
t.Fatalf("Expected NumPending to be 10, got %d", ci.NumPending)
}
}
// Issue #2662
func TestJetStreamLargeExpiresAndServerRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
maxAge := 2 * time.Second
_, err := js.AddStream(&nats.StreamConfig{
Name: "S",
Subjects: []string{"foo"},
MaxAge: maxAge,
})
require_NoError(t, err)
start := time.Now()
_, err = js.Publish("foo", []byte("ok"))
require_NoError(t, err)
// Wait total of maxAge - 1s.
time.Sleep(maxAge - time.Since(start) - time.Second)
// Stop current
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
// Restart.
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
checkFor(t, 5*time.Second, 10*time.Millisecond, func() error {
si, err := js.StreamInfo("S")
require_NoError(t, err)
if si.State.Msgs != 0 {
return fmt.Errorf("Expected no messages, got %d", si.State.Msgs)
}
return nil
})
if waited := time.Since(start); waited > maxAge+time.Second {
t.Fatalf("Waited to long %v vs %v for messages to expire", waited, maxAge)
}
}
// Bug that was reported showing memstore not handling max per subject of 1.
func TestJetStreamMessagePerSubjectKeepBug(t *testing.T) {
test := func(t *testing.T, keep int64, store nats.StorageType) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
MaxMsgsPerSubject: keep,
Storage: store,
})
require_NoError(t, err)
for i := 0; i < 100; i++ {
_, err = js.Publish("TEST", []byte(fmt.Sprintf("test %d", i)))
require_NoError(t, err)
}
nfo, err := js.StreamInfo("TEST")
require_NoError(t, err)
if nfo.State.Msgs != uint64(keep) {
t.Fatalf("Expected %d message got %d", keep, nfo.State.Msgs)
}
}
t.Run("FileStore", func(t *testing.T) {
t.Run("Keep 10", func(t *testing.T) { test(t, 10, nats.FileStorage) })
t.Run("Keep 1", func(t *testing.T) { test(t, 1, nats.FileStorage) })
})
t.Run("MemStore", func(t *testing.T) {
t.Run("Keep 10", func(t *testing.T) { test(t, 10, nats.MemoryStorage) })
t.Run("Keep 1", func(t *testing.T) { test(t, 1, nats.MemoryStorage) })
})
}
func TestJetStreamInvalidDeliverSubject(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{DeliverSubject: " x"})
require_Error(t, err, NewJSConsumerInvalidDeliverSubjectError())
}
func TestJetStreamMemoryCorruption(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
errCh := make(chan error, 10)
nc.SetErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, e error) {
select {
case errCh <- e:
default:
}
})
// The storage has to be MemoryStorage to show the issue
kv, err := js.CreateKeyValue(&nats.KeyValueConfig{Bucket: "bucket", Storage: nats.MemoryStorage})
require_NoError(t, err)
w1, err := kv.WatchAll()
require_NoError(t, err)
w2, err := kv.WatchAll(nats.MetaOnly())
require_NoError(t, err)
kv.Put("key1", []byte("aaa"))
kv.Put("key1", []byte("aab"))
kv.Put("key2", []byte("zza"))
kv.Put("key2", []byte("zzb"))
kv.Delete("key1")
kv.Delete("key2")
kv.Put("key1", []byte("aac"))
kv.Put("key2", []byte("zzc"))
kv.Delete("key1")
kv.Delete("key2")
kv.Purge("key1")
kv.Purge("key2")
checkUpdates := func(updates <-chan nats.KeyValueEntry) {
t.Helper()
count := 0
for {
select {
case <-updates:
count++
if count == 13 {
return
}
case <-time.After(time.Second):
t.Fatal("Did not receive all updates")
}
}
}
checkUpdates(w1.Updates())
checkUpdates(w2.Updates())
select {
case e := <-errCh:
t.Fatal(e)
case <-time.After(250 * time.Millisecond):
// OK
}
}
func TestJetStreamRecoverBadStreamSubjects(t *testing.T) {
s := RunBasicJetStreamServer(t)
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
f := filepath.Join(sd, "$G", "streams", "TEST")
fs, err := newFileStore(FileStoreConfig{StoreDir: f}, StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar", " baz "}, // baz has spaces
Storage: FileStorage,
})
require_NoError(t, err)
fs.Stop()
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
if len(si.Config.Subjects) != 3 {
t.Fatalf("Expected to recover all subjects")
}
}
func TestJetStreamRecoverBadMirrorConfigWithSubjects(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
sd := s.JetStreamConfig().StoreDir
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Origin
_, err := js.AddStream(&nats.StreamConfig{
Name: "S",
Subjects: []string{"foo"},
})
require_NoError(t, err)
s.Shutdown()
f := filepath.Join(sd, "$G", "streams", "M")
fs, err := newFileStore(FileStoreConfig{StoreDir: f}, StreamConfig{
Name: "M",
Subjects: []string{"foo", "bar", "baz"}, // Mirrors should not have spaces.
Mirror: &StreamSource{Name: "S"},
Storage: FileStorage,
})
require_NoError(t, err)
fs.Stop()
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
si, err := js.StreamInfo("M")
require_NoError(t, err)
if len(si.Config.Subjects) != 0 {
t.Fatalf("Expected to have NO subjects on mirror")
}
}
func TestJetStreamCrossAccountsDeliverSubjectInterest(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 4GB, max_file_store: 1TB, store_dir: %q}
accounts: {
A: {
jetstream: enabled
users: [ {user: a, password: pwd} ]
exports [
{ stream: "_d_" } # For the delivery subject for the consumer
]
},
B: {
users: [ {user: b, password: pwd} ]
imports [
{ stream: { account: A, subject: "_d_"}, to: "foo" }
]
},
}
`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
nc, js := jsClientConnect(t, s, nats.UserInfo("a", "pwd"))
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
msg, toSend := []byte("OK"), 100
for i := 0; i < toSend; i++ {
if _, err := js.PublishAsync("foo", msg); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
// Now create the consumer as well here manually that we will want to reference from Account B.
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "dlc", DeliverSubject: "_d_"})
require_NoError(t, err)
// Wait to see if the stream import signals to deliver messages with no real subscriber interest.
time.Sleep(200 * time.Millisecond)
ci, err := js.ConsumerInfo("TEST", "dlc")
require_NoError(t, err)
// Make sure we have not delivered any messages based on the import signal alone.
if ci.NumPending != uint64(toSend) || ci.Delivered.Consumer != 0 {
t.Fatalf("Bad consumer info, looks like we started delivering: %+v", ci)
}
// Now create interest in the delivery subject through the import on account B.
nc, _ = jsClientConnect(t, s, nats.UserInfo("b", "pwd"))
defer nc.Close()
sub, err := nc.SubscribeSync("foo")
require_NoError(t, err)
checkSubsPending(t, sub, toSend)
ci, err = js.ConsumerInfo("TEST", "dlc")
require_NoError(t, err)
// Make sure our consumer info reflects we delivered the messages.
if ci.NumPending != 0 || ci.Delivered.Consumer != uint64(toSend) {
t.Fatalf("Bad consumer info, looks like we did not deliver: %+v", ci)
}
}
func TestJetStreamEphemeralPullConsumers(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "EC", Storage: nats.MemoryStorage})
require_NoError(t, err)
ci, err := js.AddConsumer("EC", &nats.ConsumerConfig{AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
mset, err := s.GlobalAccount().lookupStream("EC")
require_NoError(t, err)
o := mset.lookupConsumer(ci.Name)
if o == nil {
t.Fatalf("Error looking up consumer %q", ci.Name)
}
err = o.setInActiveDeleteThreshold(50 * time.Millisecond)
require_NoError(t, err)
time.Sleep(100 * time.Millisecond)
// Should no longer be around.
if o := mset.lookupConsumer(ci.Name); o != nil {
t.Fatalf("Expected consumer to be closed and removed")
}
// Make sure timer keeps firing etc. and does not delete until interest is gone.
ci, err = js.AddConsumer("EC", &nats.ConsumerConfig{AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
if o = mset.lookupConsumer(ci.Name); o == nil {
t.Fatalf("Error looking up consumer %q", ci.Name)
}
err = o.setInActiveDeleteThreshold(50 * time.Millisecond)
require_NoError(t, err)
// Need interest otherwise the requests will be recycled based on no real interest.
sub, err := nc.SubscribeSync("xx")
require_NoError(t, err)
req := &JSApiConsumerGetNextRequest{Batch: 10, Expires: 250 * time.Millisecond}
jreq, err := json.Marshal(req)
require_NoError(t, err)
rsubj := fmt.Sprintf(JSApiRequestNextT, "EC", ci.Name)
err = nc.PublishRequest(rsubj, "xx", jreq)
require_NoError(t, err)
nc.Flush()
time.Sleep(100 * time.Millisecond)
// Should still be alive here.
if o := mset.lookupConsumer(ci.Name); o == nil {
t.Fatalf("Expected consumer to still be active")
}
// Remove interest.
sub.Unsubscribe()
// Make sure this EPC goes away now.
checkFor(t, 5*time.Second, 10*time.Millisecond, func() error {
if o := mset.lookupConsumer(ci.Name); o != nil {
return fmt.Errorf("Consumer still present")
}
return nil
})
}
func TestJetStreamEphemeralPullConsumersInactiveThresholdAndNoWait(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "ECIT", Storage: nats.MemoryStorage})
require_NoError(t, err)
ci, err := js.AddConsumer("ECIT", &nats.ConsumerConfig{
AckPolicy: nats.AckExplicitPolicy,
InactiveThreshold: 100 * time.Millisecond,
})
require_NoError(t, err)
// Send 10 no_wait requests every 25ms and consumer should still be present.
req := &JSApiConsumerGetNextRequest{Batch: 10, NoWait: true}
jreq, err := json.Marshal(req)
require_NoError(t, err)
rsubj := fmt.Sprintf(JSApiRequestNextT, "ECIT", ci.Name)
for i := 0; i < 10; i++ {
err = nc.PublishRequest(rsubj, "xx", jreq)
require_NoError(t, err)
nc.Flush()
time.Sleep(25 * time.Millisecond)
}
_, err = js.ConsumerInfo("ECIT", ci.Name)
require_NoError(t, err)
}
func TestJetStreamNakRedeliveryWithNoWait(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
_, err = js.Publish("foo", []byte("NAK"))
require_NoError(t, err)
ccReq := &CreateConsumerRequest{
Stream: "TEST",
Config: ConsumerConfig{
Durable: "dlc",
AckPolicy: AckExplicit,
MaxDeliver: 3,
AckWait: time.Minute,
BackOff: []time.Duration{5 * time.Second, 10 * time.Second},
},
}
// Do by hand for now until Go client catches up.
req, err := json.Marshal(ccReq)
require_NoError(t, err)
resp, err := nc.Request(fmt.Sprintf(JSApiDurableCreateT, "TEST", "dlc"), req, time.Second)
require_NoError(t, err)
var ccResp JSApiConsumerCreateResponse
err = json.Unmarshal(resp.Data, &ccResp)
require_NoError(t, err)
if ccResp.Error != nil {
t.Fatalf("Unexpected error: %+v", ccResp.Error)
}
rsubj := fmt.Sprintf(JSApiRequestNextT, "TEST", "dlc")
m, err := nc.Request(rsubj, nil, time.Second)
require_NoError(t, err)
// NAK this message.
delay, err := json.Marshal(&ConsumerNakOptions{Delay: 500 * time.Millisecond})
require_NoError(t, err)
dnak := []byte(fmt.Sprintf("%s %s", AckNak, delay))
m.Respond(dnak)
// This message should come back to us after 500ms. If we do a one-shot request, with NoWait and Expires
// this will do the right thing and we get the message.
// What we want to test here is a true NoWait request with Expires==0 and eventually seeing the message be redelivered.
expires := time.Now().Add(time.Second)
for time.Now().Before(expires) {
m, err = nc.Request(rsubj, []byte(`{"batch":1, "no_wait": true}`), time.Second)
require_NoError(t, err)
if len(m.Data) > 0 {
// We got our message, so we are good.
return
}
// So we do not spin.
time.Sleep(100 * time.Millisecond)
}
t.Fatalf("Did not get the message in time")
}
// Test that we properly enforce per subject msg limits when DiscardNew is set.
// DiscardNew should only apply to stream limits, subject based limits should always be DiscardOld.
func TestJetStreamMaxMsgsPerSubjectWithDiscardNew(t *testing.T) {
msc := StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar", "baz", "x"},
Discard: DiscardNew,
Storage: MemoryStorage,
MaxMsgsPer: 4,
MaxMsgs: 10,
MaxBytes: 500,
}
fsc := msc
fsc.Storage = FileStorage
cases := []struct {
name string
mconfig *StreamConfig
}{
{"MemoryStore", &msc},
{"FileStore", &fsc},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(c.mconfig)
require_NoError(t, err)
defer mset.delete()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
pubAndCheck := func(subj string, num int, expectedNumMsgs uint64) {
t.Helper()
for i := 0; i < num; i++ {
_, err = js.Publish(subj, []byte("TSLA"))
require_NoError(t, err)
}
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
if si.State.Msgs != expectedNumMsgs {
t.Fatalf("Expected %d msgs, got %d", expectedNumMsgs, si.State.Msgs)
}
}
pubExpectErr := func(subj string, sz int) {
t.Helper()
_, err = js.Publish(subj, bytes.Repeat([]byte("X"), sz))
require_Error(t, err, errors.New("nats: maximum bytes exceeded"), errors.New("nats: maximum messages exceeded"))
}
pubAndCheck("foo", 1, 1)
// We should treat this as DiscardOld and only have 4 msgs after.
pubAndCheck("foo", 4, 4)
// Same thing here, shoud only have 4 foo and 4 bar for total of 8.
pubAndCheck("bar", 8, 8)
// We have 8 here, so only 2 left. If we add in a new subject when we have room it will be accepted.
pubAndCheck("baz", 2, 10)
// Now we are full, but makeup is foo-4 bar-4 baz-2.
// We can add to foo and bar since they are at their max and adding new ones there stays the same in terms of total of 10.
pubAndCheck("foo", 1, 10)
pubAndCheck("bar", 1, 10)
// Try to send a large message under an established subject that will exceed the 500 maximum.
// Even though we have a bar subject and its at its maximum, the message to be dropped is not big enough, so this should err.
pubExpectErr("bar", 300)
// Also even though we have room bytes wise, if we introduce a new subject this should fail too on msg limit exceeded.
pubExpectErr("x", 2)
})
}
}
func TestJetStreamStreamInfoSubjectsDetails(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
getInfo := func(t *testing.T, filter string) *StreamInfo {
t.Helper()
// Need to grab StreamInfo by hand for now.
req, err := json.Marshal(&JSApiStreamInfoRequest{SubjectsFilter: filter})
require_NoError(t, err)
resp, err := nc.Request(fmt.Sprintf(JSApiStreamInfoT, "TEST"), req, time.Second)
require_NoError(t, err)
var si StreamInfo
err = json.Unmarshal(resp.Data, &si)
require_NoError(t, err)
if si.State.NumSubjects != 3 {
t.Fatalf("Expected NumSubjects to be 3, but got %d", si.State.NumSubjects)
}
return &si
}
testSubjects := func(t *testing.T, st nats.StorageType) {
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"*"},
Storage: st,
})
require_NoError(t, err)
defer js.DeleteStream("TEST")
counts, msg := []int{22, 33, 44}, []byte("ok")
// Now place msgs, foo-22, bar-33 and baz-44.
for i, subj := range []string{"foo", "bar", "baz"} {
for n := 0; n < counts[i]; n++ {
_, err = js.Publish(subj, msg)
require_NoError(t, err)
}
}
// Test all subjects first.
expected := map[string]uint64{"foo": 22, "bar": 33, "baz": 44}
if si := getInfo(t, nats.AllKeys); !reflect.DeepEqual(si.State.Subjects, expected) {
t.Fatalf("Expected subjects of %+v, but got %+v", expected, si.State.Subjects)
}
if si := getInfo(t, "*"); !reflect.DeepEqual(si.State.Subjects, expected) {
t.Fatalf("Expected subjects of %+v, but got %+v", expected, si.State.Subjects)
}
// Filtered to 1.
expected = map[string]uint64{"foo": 22}
if si := getInfo(t, "foo"); !reflect.DeepEqual(si.State.Subjects, expected) {
t.Fatalf("Expected subjects of %+v, but got %+v", expected, si.State.Subjects)
}
}
t.Run("MemoryStore", func(t *testing.T) { testSubjects(t, nats.MemoryStorage) })
t.Run("FileStore", func(t *testing.T) { testSubjects(t, nats.FileStorage) })
}
func TestJetStreamStreamInfoSubjectsDetailsWithDeleteAndPurge(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
getInfo := func(t *testing.T, filter string) *StreamInfo {
t.Helper()
// Need to grab StreamInfo by hand for now.
req, err := json.Marshal(&JSApiStreamInfoRequest{SubjectsFilter: filter})
require_NoError(t, err)
resp, err := nc.Request(fmt.Sprintf(JSApiStreamInfoT, "TEST"), req, time.Second)
require_NoError(t, err)
var si StreamInfo
err = json.Unmarshal(resp.Data, &si)
require_NoError(t, err)
return &si
}
checkResults := func(t *testing.T, expected map[string]uint64) {
t.Helper()
si := getInfo(t, nats.AllKeys)
if !reflect.DeepEqual(si.State.Subjects, expected) {
t.Fatalf("Expected subjects of %+v, but got %+v", expected, si.State.Subjects)
}
if si.State.NumSubjects != len(expected) {
t.Fatalf("Expected NumSubjects to be %d, but got %d", len(expected), si.State.NumSubjects)
}
}
testSubjects := func(t *testing.T, st nats.StorageType) {
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"*"},
Storage: st,
})
require_NoError(t, err)
defer js.DeleteStream("TEST")
msg := []byte("ok")
js.Publish("foo", msg) // 1
js.Publish("foo", msg) // 2
js.Publish("bar", msg) // 3
js.Publish("baz", msg) // 4
js.Publish("baz", msg) // 5
js.Publish("bar", msg) // 6
js.Publish("bar", msg) // 7
checkResults(t, map[string]uint64{"foo": 2, "bar": 3, "baz": 2})
// Now delete some messages.
js.DeleteMsg("TEST", 6)
checkResults(t, map[string]uint64{"foo": 2, "bar": 2, "baz": 2})
// Delete and add right back, so no-op
js.DeleteMsg("TEST", 5) // baz
js.Publish("baz", msg) // 8
checkResults(t, map[string]uint64{"foo": 2, "bar": 2, "baz": 2})
// Now do a purge only of bar.
jr, _ := json.Marshal(&JSApiStreamPurgeRequest{Subject: "bar"})
_, err = nc.Request(fmt.Sprintf(JSApiStreamPurgeT, "TEST"), jr, time.Second)
require_NoError(t, err)
checkResults(t, map[string]uint64{"foo": 2, "baz": 2})
// Now purge everything
err = js.PurgeStream("TEST")
require_NoError(t, err)
si := getInfo(t, nats.AllKeys)
if len(si.State.Subjects) != 0 {
t.Fatalf("Expected no subjects, but got %+v", si.State.Subjects)
}
if si.State.NumSubjects != 0 {
t.Fatalf("Expected NumSubjects to be 0, but got %d", si.State.NumSubjects)
}
}
t.Run("MemoryStore", func(t *testing.T) { testSubjects(t, nats.MemoryStorage) })
t.Run("FileStore", func(t *testing.T) { testSubjects(t, nats.FileStorage) })
}
func TestJetStreamStreamInfoSubjectsDetailsAfterRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
getInfo := func(t *testing.T, filter string) *StreamInfo {
t.Helper()
// Need to grab StreamInfo by hand for now.
req, err := json.Marshal(&JSApiStreamInfoRequest{SubjectsFilter: filter})
require_NoError(t, err)
resp, err := nc.Request(fmt.Sprintf(JSApiStreamInfoT, "TEST"), req, time.Second)
require_NoError(t, err)
var si StreamInfo
err = json.Unmarshal(resp.Data, &si)
require_NoError(t, err)
return &si
}
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"*"},
})
require_NoError(t, err)
defer js.DeleteStream("TEST")
msg := []byte("ok")
js.Publish("foo", msg) // 1
js.Publish("foo", msg) // 2
js.Publish("bar", msg) // 3
js.Publish("baz", msg) // 4
js.Publish("baz", msg) // 5
si := getInfo(t, nats.AllKeys)
if si.State.NumSubjects != 3 {
t.Fatalf("Expected 3 subjects, but got %d", si.State.NumSubjects)
}
// Stop current
nc.Close()
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
// Restart.
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, _ = jsClientConnect(t, s)
defer nc.Close()
si = getInfo(t, nats.AllKeys)
if si.State.NumSubjects != 3 {
t.Fatalf("Expected 3 subjects, but got %d", si.State.NumSubjects)
}
}
// Issue #2836
func TestJetStreamInterestRetentionBug(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>"},
Retention: nats.InterestPolicy,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "c1", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
test := func(token string, fseq, msgs uint64) {
t.Helper()
subj := fmt.Sprintf("foo.%s", token)
_, err = js.Publish(subj, nil)
require_NoError(t, err)
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
if si.State.FirstSeq != fseq {
t.Fatalf("Expected first to be %d, got %d", fseq, si.State.FirstSeq)
}
if si.State.Msgs != msgs {
t.Fatalf("Expected msgs to be %d, got %d", msgs, si.State.Msgs)
}
}
test("bar", 1, 1)
// Create second filtered consumer.
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "c2", FilterSubject: "foo.foo", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
test("bar", 1, 2)
}
// Under load testing for K3S and the KINE interface we saw some stalls.
// These were caused by a dynamic that would not send a second FC item with one
// pending, but when we sent the next message and got blocked, if that msg would
// exceed the outstanding FC we would become stalled.
func TestJetStreamFlowControlStall(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "FC"})
require_NoError(t, err)
msg := []byte(strings.Repeat("X", 32768))
_, err = js.Publish("FC", msg)
require_NoError(t, err)
msg = []byte(strings.Repeat("X", 8*32768))
_, err = js.Publish("FC", msg)
require_NoError(t, err)
_, err = js.Publish("FC", msg)
require_NoError(t, err)
sub, err := js.SubscribeSync("FC", nats.OrderedConsumer())
require_NoError(t, err)
checkSubsPending(t, sub, 3)
}
func TestJetStreamStorageReservedBytes(t *testing.T) {
const systemLimit = 1024
opts := DefaultTestOptions
opts.Port = -1
opts.JetStream = true
opts.JetStreamMaxMemory = systemLimit
opts.JetStreamMaxStore = systemLimit
opts.StoreDir = t.TempDir()
opts.HTTPPort = -1
s := RunServer(&opts)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
getJetStreamVarz := func(hc *http.Client, addr string) (JetStreamVarz, error) {
resp, err := hc.Get(addr)
if err != nil {
return JetStreamVarz{}, err
}
defer resp.Body.Close()
var v Varz
if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
return JetStreamVarz{}, err
}
return v.JetStream, nil
}
getReserved := func(hc *http.Client, addr string, st nats.StorageType) (uint64, error) {
jsv, err := getJetStreamVarz(hc, addr)
if err != nil {
return 0, err
}
if st == nats.MemoryStorage {
return jsv.Stats.ReservedMemory, nil
}
return jsv.Stats.ReservedStore, nil
}
varzAddr := fmt.Sprintf("http://127.0.0.1:%d/varz", s.MonitorAddr().Port)
hc := &http.Client{Timeout: 5 * time.Second}
jsv, err := getJetStreamVarz(hc, varzAddr)
require_NoError(t, err)
if got, want := systemLimit, int(jsv.Config.MaxMemory); got != want {
t.Fatalf("Unexpected max memory: got=%d, want=%d", got, want)
}
if got, want := systemLimit, int(jsv.Config.MaxStore); got != want {
t.Fatalf("Unexpected max store: got=%d, want=%d", got, want)
}
cases := []struct {
name string
accountLimit int64
storage nats.StorageType
createMaxBytes int64
updateMaxBytes int64
wantUpdateError bool
}{
{
name: "file reserve 66% of system limit",
accountLimit: -1,
storage: nats.FileStorage,
createMaxBytes: int64(math.Round(float64(systemLimit) * .666)),
updateMaxBytes: int64(math.Round(float64(systemLimit)*.666)) + 1,
},
{
name: "memory reserve 66% of system limit",
accountLimit: -1,
storage: nats.MemoryStorage,
createMaxBytes: int64(math.Round(float64(systemLimit) * .666)),
updateMaxBytes: int64(math.Round(float64(systemLimit)*.666)) + 1,
},
{
name: "file update past system limit",
accountLimit: -1,
storage: nats.FileStorage,
createMaxBytes: systemLimit,
updateMaxBytes: systemLimit + 1,
wantUpdateError: true,
},
{
name: "memory update past system limit",
accountLimit: -1,
storage: nats.MemoryStorage,
createMaxBytes: systemLimit,
updateMaxBytes: systemLimit + 1,
wantUpdateError: true,
},
{
name: "file update to system limit",
accountLimit: -1,
storage: nats.FileStorage,
createMaxBytes: systemLimit - 1,
updateMaxBytes: systemLimit,
},
{
name: "memory update to system limit",
accountLimit: -1,
storage: nats.MemoryStorage,
createMaxBytes: systemLimit - 1,
updateMaxBytes: systemLimit,
},
{
name: "file reserve 66% of account limit",
accountLimit: systemLimit / 2,
storage: nats.FileStorage,
createMaxBytes: int64(math.Round(float64(systemLimit/2) * .666)),
updateMaxBytes: int64(math.Round(float64(systemLimit/2)*.666)) + 1,
},
{
name: "memory reserve 66% of account limit",
accountLimit: systemLimit / 2,
storage: nats.MemoryStorage,
createMaxBytes: int64(math.Round(float64(systemLimit/2) * .666)),
updateMaxBytes: int64(math.Round(float64(systemLimit/2)*.666)) + 1,
},
{
name: "file update past account limit",
accountLimit: systemLimit / 2,
storage: nats.FileStorage,
createMaxBytes: (systemLimit / 2),
updateMaxBytes: (systemLimit / 2) + 1,
wantUpdateError: true,
},
{
name: "memory update past account limit",
accountLimit: systemLimit / 2,
storage: nats.MemoryStorage,
createMaxBytes: (systemLimit / 2),
updateMaxBytes: (systemLimit / 2) + 1,
wantUpdateError: true,
},
{
name: "file update to account limit",
accountLimit: systemLimit / 2,
storage: nats.FileStorage,
createMaxBytes: (systemLimit / 2) - 1,
updateMaxBytes: (systemLimit / 2),
},
{
name: "memory update to account limit",
accountLimit: systemLimit / 2,
storage: nats.MemoryStorage,
createMaxBytes: (systemLimit / 2) - 1,
updateMaxBytes: (systemLimit / 2),
},
}
for i := 0; i < len(cases) && !t.Failed(); i++ {
c := cases[i]
t.Run(c.name, func(st *testing.T) {
// Setup limits
err = s.GlobalAccount().UpdateJetStreamLimits(map[string]JetStreamAccountLimits{
_EMPTY_: {
MaxMemory: c.accountLimit,
MaxStore: c.accountLimit,
},
})
require_NoError(st, err)
// Create initial stream
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: c.storage,
MaxBytes: c.createMaxBytes,
}
_, err = js.AddStream(cfg)
require_NoError(st, err)
// Update stream MaxBytes
cfg.MaxBytes = c.updateMaxBytes
info, err := js.UpdateStream(cfg)
if c.wantUpdateError && err == nil {
got := info.Config.MaxBytes
st.Fatalf("Unexpected update success, newMaxBytes=%d; systemLimit=%d; accountLimit=%d",
got, systemLimit, c.accountLimit)
} else if !c.wantUpdateError && err != nil {
st.Fatalf("Unexpected update error: %s", err)
}
if !c.wantUpdateError && err == nil {
// If update was successful, then ensure reserved shows new
// amount
reserved, err := getReserved(hc, varzAddr, c.storage)
require_NoError(st, err)
if got, want := reserved, uint64(c.updateMaxBytes); got != want {
st.Fatalf("Unexpected reserved: %d, want %d", got, want)
}
}
// Delete stream
err = js.DeleteStream("TEST")
require_NoError(st, err)
// Ensure reserved shows 0 because we've deleted the stream
reserved, err := getReserved(hc, varzAddr, c.storage)
require_NoError(st, err)
if reserved != 0 {
st.Fatalf("Unexpected reserved: %d, want 0", reserved)
}
})
}
}
func TestJetStreamRestoreBadStream(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
var rreq JSApiStreamRestoreRequest
buf, err := os.ReadFile("../test/configs/jetstream/restore_bad_stream/backup.json")
require_NoError(t, err)
err = json.Unmarshal(buf, &rreq)
require_NoError(t, err)
data, err := os.Open("../test/configs/jetstream/restore_bad_stream/stream.tar.s2")
require_NoError(t, err)
defer data.Close()
var rresp JSApiStreamRestoreResponse
msg, err := nc.Request(fmt.Sprintf(JSApiStreamRestoreT, rreq.Config.Name), buf, 5*time.Second)
require_NoError(t, err)
json.Unmarshal(msg.Data, &rresp)
if rresp.Error != nil {
t.Fatalf("Error on restore: %+v", rresp.Error)
}
var chunk [1024]byte
gotChunkErr := false
for {
n, err := data.Read(chunk[:])
if err == io.EOF {
break
}
require_NoError(t, err)
msg, err = nc.Request(rresp.DeliverSubject, chunk[:n], 5*time.Second)
require_NoError(t, err)
if len(msg.Data) == 0 {
continue
}
require_False(t, bytes.Equal(msg.Data, []byte("-ERR 'restore stalled'")))
json.Unmarshal(msg.Data, &rresp)
if rresp.Error != nil {
require_Contains(t, rresp.Error.Description, "unexpected")
gotChunkErr = true
break
}
}
if !gotChunkErr {
msg, err = nc.Request(rresp.DeliverSubject, nil, 5*time.Second)
require_NoError(t, err)
require_False(t, bytes.Equal(msg.Data, []byte("-ERR 'restore stalled'")))
json.Unmarshal(msg.Data, &rresp)
require_True(t, rresp.Error != nil)
require_Contains(t, rresp.Error.Description, "unexpected")
}
dir := filepath.Join(s.JetStreamConfig().StoreDir, globalAccountName)
f1 := filepath.Join(dir, "fail1.txt")
f2 := filepath.Join(dir, "fail2.txt")
for _, f := range []string{f1, f2} {
if _, err := os.Stat(f); err == nil {
t.Fatalf("Found file %s", f)
}
}
}
func TestJetStreamRemoveExternalSource(t *testing.T) {
ho := DefaultTestOptions
ho.Port = 4000 //-1
ho.LeafNode.Host = "127.0.0.1"
ho.LeafNode.Port = -1
hs := RunServer(&ho)
defer hs.Shutdown()
lu, err := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", ho.LeafNode.Port))
require_NoError(t, err)
lo1 := DefaultTestOptions
lo1.Port = 4111 //-1
lo1.ServerName = "a-leaf"
lo1.JetStream = true
lo1.StoreDir = t.TempDir()
lo1.JetStreamDomain = "a-leaf"
lo1.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{lu}}}
l1 := RunServer(&lo1)
defer l1.Shutdown()
lo2 := DefaultTestOptions
lo2.Port = 2111 //-1
lo2.ServerName = "b-leaf"
lo2.JetStream = true
lo2.StoreDir = t.TempDir()
lo2.JetStreamDomain = "b-leaf"
lo2.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{lu}}}
l2 := RunServer(&lo2)
defer l2.Shutdown()
checkLeafNodeConnected(t, l1)
checkLeafNodeConnected(t, l2)
checkStreamMsgs := func(js nats.JetStreamContext, stream string, expected uint64) {
t.Helper()
checkFor(t, 2*time.Second, 15*time.Millisecond, func() error {
si, err := js.StreamInfo(stream)
if err != nil {
return err
}
if si.State.Msgs != expected {
return fmt.Errorf("Expected %v messages, got %v", expected, si.State.Msgs)
}
return nil
})
}
sendToStreamTest := func(js nats.JetStreamContext) {
t.Helper()
for i := 0; i < 10; i++ {
_, err = js.Publish("test", []byte("hello"))
require_NoError(t, err)
}
}
nca, jsa := jsClientConnect(t, l1)
defer nca.Close()
_, err = jsa.AddStream(&nats.StreamConfig{Name: "queue", Subjects: []string{"queue"}})
require_NoError(t, err)
_, err = jsa.AddStream(&nats.StreamConfig{Name: "testdel", Subjects: []string{"testdel"}})
require_NoError(t, err)
ncb, jsb := jsClientConnect(t, l2)
defer ncb.Close()
_, err = jsb.AddStream(&nats.StreamConfig{Name: "test", Subjects: []string{"test"}})
require_NoError(t, err)
sendToStreamTest(jsb)
checkStreamMsgs(jsb, "test", 10)
_, err = jsb.AddStream(&nats.StreamConfig{Name: "testdelsrc1", Subjects: []string{"testdelsrc1"}})
require_NoError(t, err)
_, err = jsb.AddStream(&nats.StreamConfig{Name: "testdelsrc2", Subjects: []string{"testdelsrc2"}})
require_NoError(t, err)
// Add test as source to queue
si, err := jsa.UpdateStream(&nats.StreamConfig{
Name: "queue",
Subjects: []string{"queue"},
Sources: []*nats.StreamSource{
{
Name: "test",
External: &nats.ExternalStream{
APIPrefix: "$JS.b-leaf.API",
},
},
},
})
require_NoError(t, err)
require_True(t, len(si.Config.Sources) == 1)
checkStreamMsgs(jsa, "queue", 10)
// add more entries to "test"
sendToStreamTest(jsb)
// verify entries are both in "test" and "queue"
checkStreamMsgs(jsb, "test", 20)
checkStreamMsgs(jsa, "queue", 20)
// Remove source
si, err = jsa.UpdateStream(&nats.StreamConfig{
Name: "queue",
Subjects: []string{"queue"},
})
require_NoError(t, err)
require_True(t, len(si.Config.Sources) == 0)
// add more entries to "test"
sendToStreamTest(jsb)
// verify entries are in "test"
checkStreamMsgs(jsb, "test", 30)
// But they should not be in "queue". We will wait a bit before checking
// to make sure that we are letting enough time for the sourcing to
// incorrectly happen if there is a bug.
time.Sleep(250 * time.Millisecond)
checkStreamMsgs(jsa, "queue", 20)
// Test that we delete correctly. First add source to a "testdel"
si, err = jsa.UpdateStream(&nats.StreamConfig{
Name: "testdel",
Subjects: []string{"testdel"},
Sources: []*nats.StreamSource{
{
Name: "testdelsrc1",
External: &nats.ExternalStream{
APIPrefix: "$JS.b-leaf.API",
},
},
},
})
require_NoError(t, err)
require_True(t, len(si.Config.Sources) == 1)
// Now add the second one...
si, err = jsa.UpdateStream(&nats.StreamConfig{
Name: "testdel",
Subjects: []string{"testdel"},
Sources: []*nats.StreamSource{
{
Name: "testdelsrc1",
External: &nats.ExternalStream{
APIPrefix: "$JS.b-leaf.API",
},
},
{
Name: "testdelsrc2",
External: &nats.ExternalStream{
APIPrefix: "$JS.b-leaf.API",
},
},
},
})
require_NoError(t, err)
require_True(t, len(si.Config.Sources) == 2)
// Now check that the stream testdel has still 2 source consumers...
acc, err := l1.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("testdel")
require_NoError(t, err)
mset.mu.RLock()
n := len(mset.sources)
mset.mu.RUnlock()
if n != 2 {
t.Fatalf("Expected 2 sources, got %v", n)
}
// Restart leaf "a"
nca.Close()
l1.Shutdown()
l1 = RunServer(&lo1)
defer l1.Shutdown()
// add more entries to "test"
sendToStreamTest(jsb)
checkStreamMsgs(jsb, "test", 40)
nca, jsa = jsClientConnect(t, l1)
defer nca.Close()
time.Sleep(250 * time.Millisecond)
checkStreamMsgs(jsa, "queue", 20)
}
func TestJetStreamAddStreamWithFilestoreFailure(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Cause failure to create stream with filestore.
// In one of ipQueue changes, this could cause a panic, so verify that we get
// a failure to create, but no panic.
if _, err := s.globalAccount().addStreamWithStore(
&StreamConfig{Name: "TEST"},
&FileStoreConfig{BlockSize: 2 * maxBlockSize}); err == nil {
t.Fatal("Expected failure, did not get one")
}
}
type checkFastState struct {
count int64
StreamStore
}
func (s *checkFastState) FastState(state *StreamState) {
// Keep track only when called from checkPending()
if bytes.Contains(debug.Stack(), []byte("checkPending(")) {
atomic.AddInt64(&s.count, 1)
}
s.StreamStore.FastState(state)
}
func TestJetStreamBackOffCheckPending(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: "TEST", Subjects: []string{"foo"}})
if err != nil {
t.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
// Plug or store to see how many times we invoke FastState, which is done in checkPending
mset.mu.Lock()
st := &checkFastState{StreamStore: mset.store}
mset.store = st
mset.mu.Unlock()
nc := clientConnectToServer(t, s)
defer nc.Close()
sendStreamMsg(t, nc, "foo", "Hello World!")
sub, _ := nc.SubscribeSync(nats.NewInbox())
defer sub.Unsubscribe()
nc.Flush()
o, err := mset.addConsumer(&ConsumerConfig{
DeliverSubject: sub.Subject,
AckPolicy: AckExplicit,
MaxDeliver: 1000,
BackOff: []time.Duration{50 * time.Millisecond, 250 * time.Millisecond, time.Second},
})
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
defer o.delete()
// Check the first delivery and the following 2 redeliveries
start := time.Now()
natsNexMsg(t, sub, time.Second)
if dur := time.Since(start); dur >= 50*time.Millisecond {
t.Fatalf("Expected first delivery to be fast, took: %v", dur)
}
start = time.Now()
natsNexMsg(t, sub, time.Second)
if dur := time.Since(start); dur < 25*time.Millisecond || dur > 75*time.Millisecond {
t.Fatalf("Expected first redelivery to be ~50ms, took: %v", dur)
}
start = time.Now()
natsNexMsg(t, sub, time.Second)
if dur := time.Since(start); dur < 200*time.Millisecond || dur > 300*time.Millisecond {
t.Fatalf("Expected first redelivery to be ~250ms, took: %v", dur)
}
// There was a bug that would cause checkPending to be invoked based on the
// ackWait (which in this case would be the first value of BackOff, which
// is 50ms). So we would call checkPending() too many times.
time.Sleep(500 * time.Millisecond)
// Check now, it should have been invoked 2/3 times.
if n := atomic.LoadInt64(&st.count); n < 2 || n > 3 {
t.Fatalf("Expected checkPending to be invoked 2/3 times, was %v", n)
}
}
func TestJetStreamCrossAccounts(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream {
store_dir = %q
}
accounts: {
A: {
users: [ {user: a, password: a} ]
jetstream: enabled
exports: [
{service: '$JS.API.>' }
{service: '$KV.>'}
{stream: 'accI.>'}
]
},
I: {
users: [ {user: i, password: i} ]
imports: [
{service: {account: A, subject: '$JS.API.>'}, to: 'fromA.>' }
{service: {account: A, subject: '$KV.>'}, to: 'fromA.$KV.>' }
{stream: {subject: 'accI.>', account: A}}
]
}
}`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
watchNext := func(w nats.KeyWatcher) nats.KeyValueEntry {
t.Helper()
select {
case e := <-w.Updates():
return e
case <-time.After(time.Second):
t.Fatal("Fail to get the next update")
}
return nil
}
nc1, js1 := jsClientConnect(t, s, nats.UserInfo("a", "a"))
defer nc1.Close()
kv1, err := js1.CreateKeyValue(&nats.KeyValueConfig{Bucket: "Map", History: 10})
if err != nil {
t.Fatalf("Error creating kv store: %v", err)
}
w1, err := kv1.Watch("map")
if err != nil {
t.Fatalf("Error creating watcher: %v", err)
}
if e := watchNext(w1); e != nil {
t.Fatalf("Expected nil entry, got %+v", e)
}
nc2, err := nats.Connect(s.ClientURL(), nats.UserInfo("i", "i"), nats.CustomInboxPrefix("accI"))
if err != nil {
t.Fatalf("Error on connect: %v", err)
}
defer nc2.Close()
js2, err := nc2.JetStream(nats.APIPrefix("fromA"))
if err != nil {
t.Fatalf("Error getting jetstream context: %v", err)
}
kv2, err := js2.CreateKeyValue(&nats.KeyValueConfig{Bucket: "Map", History: 10})
if err != nil {
t.Fatalf("Error creating kv store: %v", err)
}
w2, err := kv2.Watch("map")
if err != nil {
t.Fatalf("Error creating watcher: %v", err)
}
if e := watchNext(w2); e != nil {
t.Fatalf("Expected nil entry, got %+v", e)
}
// Do a Put from kv2
rev, err := kv2.Put("map", []byte("value"))
if err != nil {
t.Fatalf("Error on put: %v", err)
}
// Get from kv1
e, err := kv1.Get("map")
if err != nil {
t.Fatalf("Error on get: %v", err)
}
if e.Key() != "map" || string(e.Value()) != "value" {
t.Fatalf("Unexpected entry: +%v", e)
}
// Get from kv2
e, err = kv2.Get("map")
if err != nil {
t.Fatalf("Error on get: %v", err)
}
if e.Key() != "map" || string(e.Value()) != "value" {
t.Fatalf("Unexpected entry: +%v", e)
}
// Watcher 1
if e := watchNext(w1); e == nil || e.Key() != "map" || string(e.Value()) != "value" {
t.Fatalf("Unexpected entry: %+v", e)
}
// Watcher 2
if e := watchNext(w2); e == nil || e.Key() != "map" || string(e.Value()) != "value" {
t.Fatalf("Unexpected entry: %+v", e)
}
// Try an update form kv2
if _, err := kv2.Update("map", []byte("updated"), rev); err != nil {
t.Fatalf("Failed to update: %v", err)
}
// Get from kv1
e, err = kv1.Get("map")
if err != nil {
t.Fatalf("Error on get: %v", err)
}
if e.Key() != "map" || string(e.Value()) != "updated" {
t.Fatalf("Unexpected entry: +%v", e)
}
// Get from kv2
e, err = kv2.Get("map")
if err != nil {
t.Fatalf("Error on get: %v", err)
}
if e.Key() != "map" || string(e.Value()) != "updated" {
t.Fatalf("Unexpected entry: +%v", e)
}
// Watcher 1
if e := watchNext(w1); e == nil || e.Key() != "map" || string(e.Value()) != "updated" {
t.Fatalf("Unexpected entry: %+v", e)
}
// Watcher 2
if e := watchNext(w2); e == nil || e.Key() != "map" || string(e.Value()) != "updated" {
t.Fatalf("Unexpected entry: %+v", e)
}
// Purge from kv2
if err := kv2.Purge("map"); err != nil {
t.Fatalf("Error on purge: %v", err)
}
// Check purge ok from w1
if e := watchNext(w1); e == nil || e.Operation() != nats.KeyValuePurge {
t.Fatalf("Unexpected entry: %+v", e)
}
// Check purge ok from w2
if e := watchNext(w2); e == nil || e.Operation() != nats.KeyValuePurge {
t.Fatalf("Unexpected entry: %+v", e)
}
// Delete purge records from kv2
if err := kv2.PurgeDeletes(nats.DeleteMarkersOlderThan(-1)); err != nil {
t.Fatalf("Error on purge deletes: %v", err)
}
// Check all gone from js1
if si, err := js1.StreamInfo("KV_Map"); err != nil || si == nil || si.State.Msgs != 0 {
t.Fatalf("Error getting stream info: err=%v si=%+v", err, si)
}
// Delete key from kv2
if err := kv2.Delete("map"); err != nil {
t.Fatalf("Error on delete: %v", err)
}
// Check key gone from kv1
if e, err := kv1.Get("map"); err != nats.ErrKeyNotFound || e != nil {
t.Fatalf("Expected key not found, got err=%v e=%+v", err, e)
}
}
func TestJetStreamInvalidRestoreRequests(t *testing.T) {
test := func(t *testing.T, s *Server, replica int) {
nc := natsConnect(t, s.ClientURL())
defer nc.Close()
// test invalid stream config in restore request
require_fail := func(cfg StreamConfig, errDesc string) {
t.Helper()
rreq := &JSApiStreamRestoreRequest{
Config: cfg,
}
req, err := json.Marshal(rreq)
require_NoError(t, err)
rmsg, err := nc.Request(fmt.Sprintf(JSApiStreamRestoreT, "fail"), req, time.Second)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var rresp JSApiStreamRestoreResponse
json.Unmarshal(rmsg.Data, &rresp)
require_True(t, rresp.Error != nil)
require_Equal(t, rresp.Error.Description, errDesc)
}
require_fail(StreamConfig{Name: "fail", MaxBytes: 1024, Storage: FileStorage, Replicas: 6},
"maximum replicas is 5")
require_fail(StreamConfig{Name: "fail", MaxBytes: 2 * 1012 * 1024, Storage: FileStorage, Replicas: replica},
"insufficient storage resources available")
js, err := nc.JetStream()
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{Name: "stream", MaxBytes: 1024, Storage: nats.FileStorage, Replicas: 1})
require_NoError(t, err)
require_fail(StreamConfig{Name: "fail", MaxBytes: 1024, Storage: FileStorage},
"maximum number of streams reached")
}
commonAccSection := `
no_auth_user: u
accounts {
ONE {
users = [ { user: "u", pass: "s3cr3t!" } ]
jetstream: {
max_store: 1Mb
max_streams: 1
}
}
$SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }
}`
t.Run("clustered", func(t *testing.T) {
c := createJetStreamClusterWithTemplate(t, `
listen: 127.0.0.1:-1
server_name: %s
jetstream: {
max_mem_store: 2MB,
max_file_store: 8MB,
store_dir: '%s',
}
cluster {
name: %s
listen: 127.0.0.1:%d
routes = [%s]
}`+commonAccSection, "clust", 3)
defer c.shutdown()
s := c.randomServer()
test(t, s, 3)
})
t.Run("single", func(t *testing.T) {
storeDir := t.TempDir()
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 2MB, max_file_store: 8MB, store_dir: '%s'}
%s`, storeDir, commonAccSection)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
test(t, s, 1)
})
}
func TestJetStreamLimits(t *testing.T) {
test := func(t *testing.T, s *Server) {
nc := natsConnect(t, s.ClientURL())
defer nc.Close()
js, err := nc.JetStream()
require_NoError(t, err)
si, err := js.AddStream(&nats.StreamConfig{Name: "foo"})
require_NoError(t, err)
require_True(t, si.Config.Duplicates == time.Minute)
si, err = js.AddStream(&nats.StreamConfig{Name: "bar", Duplicates: 1500 * time.Millisecond})
require_NoError(t, err)
require_True(t, si.Config.Duplicates == 1500*time.Millisecond)
_, err = js.UpdateStream(&nats.StreamConfig{Name: "bar", Duplicates: 2 * time.Minute})
require_Error(t, err)
require_Equal(t, err.Error(), "nats: duplicates window can not be larger then server limit of 1m0s")
_, err = js.AddStream(&nats.StreamConfig{Name: "baz", Duplicates: 2 * time.Minute})
require_Error(t, err)
require_Equal(t, err.Error(), "nats: duplicates window can not be larger then server limit of 1m0s")
ci, err := js.AddConsumer("foo", &nats.ConsumerConfig{Durable: "dur1", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
require_True(t, ci.Config.MaxAckPending == 1000)
require_True(t, ci.Config.MaxRequestBatch == 250)
_, err = js.AddConsumer("foo", &nats.ConsumerConfig{Durable: "dur2", AckPolicy: nats.AckExplicitPolicy, MaxRequestBatch: 500})
require_Error(t, err)
require_Equal(t, err.Error(), "nats: consumer max request batch exceeds server limit of 250")
ci, err = js.AddConsumer("foo", &nats.ConsumerConfig{Durable: "dur2", AckPolicy: nats.AckExplicitPolicy, MaxAckPending: 500})
require_NoError(t, err)
require_True(t, ci.Config.MaxAckPending == 500)
require_True(t, ci.Config.MaxRequestBatch == 250)
_, err = js.UpdateConsumer("foo", &nats.ConsumerConfig{Durable: "dur2", AckPolicy: nats.AckExplicitPolicy, MaxAckPending: 2000})
require_Error(t, err)
require_Equal(t, err.Error(), "nats: consumer max ack pending exceeds system limit of 1000")
_, err = js.AddConsumer("foo", &nats.ConsumerConfig{Durable: "dur3", AckPolicy: nats.AckExplicitPolicy, MaxAckPending: 2000})
require_Error(t, err)
require_Equal(t, err.Error(), "nats: consumer max ack pending exceeds system limit of 1000")
}
t.Run("clustered", func(t *testing.T) {
tmpl := `
listen: 127.0.0.1:-1
server_name: %s
jetstream: {
max_mem_store: 2MB,
max_file_store: 8MB,
store_dir: '%s',
limits: {duplicate_window: "1m", max_request_batch: 250}
}
cluster {
name: %s
listen: 127.0.0.1:%d
routes = [%s]
}
no_auth_user: u
accounts {
ONE {
users = [ { user: "u", pass: "s3cr3t!" } ]
jetstream: enabled
}
$SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }
}`
limitsTest := func(t *testing.T, tmpl string) {
c := createJetStreamClusterWithTemplate(t, tmpl, "clust", 3)
defer c.shutdown()
s := c.randomServer()
test(t, s)
}
// test with max_ack_pending being defined in operator or account
t.Run("operator", func(t *testing.T) {
limitsTest(t, strings.Replace(tmpl, "duplicate_window", "max_ack_pending: 1000, duplicate_window", 1))
})
t.Run("account", func(t *testing.T) {
limitsTest(t, strings.Replace(tmpl, "jetstream: enabled", "jetstream: {max_ack_pending: 1000}", 1))
})
})
t.Run("single", func(t *testing.T) {
tmpl := `
listen: 127.0.0.1:-1
jetstream: {
max_mem_store: 2MB,
max_file_store: 8MB,
store_dir: '%s',
limits: {duplicate_window: "1m", max_request_batch: 250}
}
no_auth_user: u
accounts {
ONE {
users = [ { user: "u", pass: "s3cr3t!" } ]
jetstream: enabled
}
$SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }
}`
limitsTest := func(t *testing.T, tmpl string) {
storeDir := t.TempDir()
conf := createConfFile(t, []byte(fmt.Sprintf(tmpl, storeDir)))
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
require_True(t, opts.JetStreamLimits.Duplicates == time.Minute)
test(t, s)
}
// test with max_ack_pending being defined in operator or account
t.Run("operator", func(t *testing.T) {
limitsTest(t, strings.Replace(tmpl, "duplicate_window", "max_ack_pending: 1000, duplicate_window", 1))
})
t.Run("account", func(t *testing.T) {
limitsTest(t, strings.Replace(tmpl, "jetstream: enabled", "jetstream: {max_ack_pending: 1000}", 1))
})
})
}
func TestJetStreamImportReload(t *testing.T) {
storeDir := t.TempDir()
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 2MB, max_file_store: 8MB, store_dir: '%s'}
accounts: {
account_a: {
users: [{user: user_a, password: pwd}]
exports: [{stream: news.>}]
}
account_b: {
users: [{user: user_b, password: pwd}]
jetstream: enabled
imports: [{stream: {subject: news.>, account: account_a}}]
}
}`, storeDir)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
ncA := natsConnect(t, s.ClientURL(), nats.UserInfo("user_a", "pwd"))
defer ncA.Close()
ncB := natsConnect(t, s.ClientURL(), nats.UserInfo("user_b", "pwd"))
defer ncB.Close()
jsB, err := ncB.JetStream()
require_NoError(t, err)
_, err = jsB.AddStream(&nats.StreamConfig{Name: "news", Subjects: []string{"news.>"}})
require_NoError(t, err)
require_NoError(t, ncA.Publish("news.article", nil))
checkFor(t, time.Second, 100*time.Millisecond, func() error {
si, err := jsB.StreamInfo("news")
if err != nil {
return err
}
if si.State.Msgs != 1 {
return fmt.Errorf("require uint64 equal, but got: %d != 1", si.State.Msgs)
}
return nil
})
// Remove exports/imports
reloadUpdateConfig(t, s, conf, fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 2MB, max_file_store: 8MB, store_dir: '%s'}
accounts: {
account_a: {
users: [{user: user_a, password: pwd}]
}
account_b: {
users: [{user: user_b, password: pwd}]
jetstream: enabled
}
}`, storeDir))
require_NoError(t, ncA.Publish("news.article", nil))
require_NoError(t, ncA.Flush())
si, err := jsB.StreamInfo("news")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 1)
}
func TestJetStreamRecoverSealedAfterServerRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "foo"})
require_NoError(t, err)
for i := 0; i < 100; i++ {
js.PublishAsync("foo", []byte("OK"))
}
<-js.PublishAsyncComplete()
_, err = js.UpdateStream(&nats.StreamConfig{Name: "foo", Sealed: true})
require_NoError(t, err)
nc.Close()
// Stop current
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
// Restart.
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
si, err := js.StreamInfo("foo")
require_NoError(t, err)
require_True(t, si.State.Msgs == 100)
}
func TestJetStreamImportConsumerStreamSubjectRemapSingle(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 4GB, max_file_store: 1TB, store_dir: %q}
accounts: {
JS: {
jetstream: enabled
users: [ {user: js, password: pwd} ]
exports [
# This is streaming to a delivery subject for a push based consumer.
{ stream: "deliver.*" }
{ stream: "foo.*" }
]
},
IM: {
users: [ {user: im, password: pwd} ]
imports [
{ stream: { account: JS, subject: "deliver.ORDERS" }, to: "d.*" }
{ stream: { account: JS, subject: "foo.*" }, to: "bar.*" }
]
},
}
`, t.TempDir())))
test := func(t *testing.T, queue bool) {
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
nc, js := jsClientConnect(t, s, nats.UserInfo("js", "pwd"))
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "ORDERS",
Subjects: []string{"foo"}, // The JS subject.
Storage: nats.MemoryStorage},
)
require_NoError(t, err)
_, err = js.Publish("foo", []byte("OK"))
require_NoError(t, err)
queueName := ""
if queue {
queueName = "queue"
}
_, err = js.AddConsumer("ORDERS", &nats.ConsumerConfig{
DeliverSubject: "deliver.ORDERS",
AckPolicy: nats.AckExplicitPolicy,
DeliverGroup: queueName,
})
require_NoError(t, err)
nc2, err := nats.Connect(s.ClientURL(), nats.UserInfo("im", "pwd"))
require_NoError(t, err)
defer nc2.Close()
var sub *nats.Subscription
if queue {
sub, err = nc2.QueueSubscribeSync("d.ORDERS", queueName)
require_NoError(t, err)
} else {
sub, err = nc2.SubscribeSync("d.ORDERS")
require_NoError(t, err)
}
m, err := sub.NextMsg(time.Second)
require_NoError(t, err)
if m.Subject != "foo" {
t.Fatalf("Subject not mapped correctly across account boundary, expected %q got %q", "foo", m.Subject)
}
// Now do one that would kick in a transform.
_, err = js.AddConsumer("ORDERS", &nats.ConsumerConfig{
DeliverSubject: "foo.ORDERS",
AckPolicy: nats.AckExplicitPolicy,
DeliverGroup: queueName,
})
require_NoError(t, err)
if queue {
sub, err = nc2.QueueSubscribeSync("bar.ORDERS", queueName)
require_NoError(t, err)
} else {
sub, err = nc2.SubscribeSync("bar.ORDERS")
require_NoError(t, err)
}
m, err = sub.NextMsg(time.Second)
require_NoError(t, err)
if m.Subject != "foo" {
t.Fatalf("Subject not mapped correctly across account boundary, expected %q got %q", "foo", m.Subject)
}
}
t.Run("noqueue", func(t *testing.T) {
test(t, false)
})
t.Run("queue", func(t *testing.T) {
test(t, true)
})
}
func TestJetStreamWorkQueueSourceRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
sent := 10
_, err := js.AddStream(&nats.StreamConfig{
Name: "FOO",
Replicas: 1,
Subjects: []string{"foo"},
})
require_NoError(t, err)
for i := 0; i < sent; i++ {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST",
Replicas: 1,
// TODO test will pass when retention commented out
Retention: nats.WorkQueuePolicy,
Sources: []*nats.StreamSource{{Name: "FOO"}}})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "dur", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
sub, err := js.PullSubscribe("foo", "dur", nats.BindStream("TEST"))
require_NoError(t, err)
time.Sleep(1 * time.Second)
ci, err := js.ConsumerInfo("TEST", "dur")
require_NoError(t, err)
require_True(t, ci.NumPending == uint64(sent))
msgs, err := sub.Fetch(sent)
require_NoError(t, err)
require_True(t, len(msgs) == sent)
for i := 0; i < sent; i++ {
err = msgs[i].AckSync()
require_NoError(t, err)
}
ci, err = js.ConsumerInfo("TEST", "dur")
require_NoError(t, err)
require_True(t, ci.NumPending == 0)
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_True(t, si.State.Msgs == 0)
// Restart server
nc.Close()
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
time.Sleep(200 * time.Millisecond)
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
checkFor(t, 10*time.Second, 200*time.Millisecond, func() error {
hs := s.healthz(nil)
if hs.Status == "ok" && hs.Error == _EMPTY_ {
return nil
}
return fmt.Errorf("healthz %s %s", hs.Error, hs.Status)
})
nc, js = jsClientConnect(t, s)
defer nc.Close()
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
if si.State.Msgs != 0 {
t.Fatalf("Expected 0 messages on restart, got %d", si.State.Msgs)
}
ctest, err := js.ConsumerInfo("TEST", "dur")
require_NoError(t, err)
//TODO (mh) I have experienced in other tests that NumPending has a value of 1 post restart.
// seems to go awary in single server setup. It's also unrelated to work queue
// but that error seems benign.
if ctest.NumPending != 0 {
t.Fatalf("Expected pending of 0 but got %d", ctest.NumPending)
}
sub, err = js.PullSubscribe("foo", "dur", nats.BindStream("TEST"))
require_NoError(t, err)
_, err = sub.Fetch(1, nats.MaxWait(time.Second))
if err != nats.ErrTimeout {
require_NoError(t, err)
}
}
func TestJetStreamWorkQueueSourceNamingRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "C1", Subjects: []string{"foo.*"}})
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{Name: "C2", Subjects: []string{"bar.*"}})
require_NoError(t, err)
sendCount := 10
for i := 0; i < sendCount; i++ {
_, err = js.Publish(fmt.Sprintf("foo.%d", i), nil)
require_NoError(t, err)
_, err = js.Publish(fmt.Sprintf("bar.%d", i), nil)
require_NoError(t, err)
}
// TODO Test will always pass if pending is 0
pending := 1
// For some yet unknown reason this failure seems to require 2 streams to source from.
// This might possibly be timing, as the test sometimes passes
streams := 2
totalPending := uint64(streams * pending)
totalMsgs := streams * sendCount
totalNonPending := streams * (sendCount - pending)
// TODO Test will always pass if this is named A (go returns directory names sorted)
// A: this stream is recovered BEFORE C1/C2, tbh, I'd expect this to be the case to fail, but it isn't
// D: this stream is recovered AFTER C1/C2, which is the case that fails (perhaps it is timing)
srcName := "D"
_, err = js.AddStream(&nats.StreamConfig{
Name: srcName,
Retention: nats.WorkQueuePolicy,
Sources: []*nats.StreamSource{{Name: "C1"}, {Name: "C2"}},
})
require_NoError(t, err)
// Add a consumer and consume all but totalPending messages
_, err = js.AddConsumer(srcName, &nats.ConsumerConfig{Durable: "dur", AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
sub, err := js.PullSubscribe("", "dur", nats.BindStream(srcName))
require_NoError(t, err)
checkFor(t, 5*time.Second, time.Millisecond*200, func() error {
if ci, err := js.ConsumerInfo(srcName, "dur"); err != nil {
return err
} else if ci.NumPending != uint64(totalMsgs) {
return fmt.Errorf("not enough messages: %d", ci.NumPending)
}
return nil
})
// consume all but messages we want pending
msgs, err := sub.Fetch(totalNonPending)
require_NoError(t, err)
require_True(t, len(msgs) == totalNonPending)
for _, m := range msgs {
err = m.AckSync()
require_NoError(t, err)
}
ci, err := js.ConsumerInfo(srcName, "dur")
require_NoError(t, err)
require_True(t, ci.NumPending == totalPending)
si, err := js.StreamInfo(srcName)
require_NoError(t, err)
require_True(t, si.State.Msgs == totalPending)
// Restart server
nc.Close()
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
time.Sleep(200 * time.Millisecond)
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
checkFor(t, 10*time.Second, 200*time.Millisecond, func() error {
hs := s.healthz(nil)
if hs.Status == "ok" && hs.Error == _EMPTY_ {
return nil
}
return fmt.Errorf("healthz %s %s", hs.Error, hs.Status)
})
nc, js = jsClientConnect(t, s)
defer nc.Close()
si, err = js.StreamInfo(srcName)
require_NoError(t, err)
if si.State.Msgs != totalPending {
t.Fatalf("Expected 0 messages on restart, got %d", si.State.Msgs)
}
}
func TestJetStreamDisabledHealthz(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
if !s.JetStreamEnabled() {
t.Fatalf("Expected JetStream to be enabled")
}
s.DisableJetStream()
hs := s.healthz(&HealthzOptions{JSEnabledOnly: true})
if hs.Status == "unavailable" && hs.Error == NewJSNotEnabledError().Error() {
return
}
t.Fatalf("Expected healthz to return error if JetStream is disabled, got status: %s", hs.Status)
}
func TestJetStreamStreamRepublishCycle(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
// Do by hand for now.
cfg := &StreamConfig{
Name: "RPC",
Storage: MemoryStorage,
Subjects: []string{"foo.>", "bar.*", "baz"},
}
expectFail := func() {
t.Helper()
req, err := json.Marshal(cfg)
require_NoError(t, err)
rmsg, err := nc.Request(fmt.Sprintf(JSApiStreamCreateT, cfg.Name), req, time.Second)
require_NoError(t, err)
var resp JSApiStreamCreateResponse
err = json.Unmarshal(rmsg.Data, &resp)
require_NoError(t, err)
if resp.Type != JSApiStreamCreateResponseType {
t.Fatalf("Invalid response type %s expected %s", resp.Type, JSApiStreamCreateResponseType)
}
if resp.Error == nil {
t.Fatalf("Expected error but got none")
}
if !strings.Contains(resp.Error.Description, "republish destination forms a cycle") {
t.Fatalf("Expected cycle error, got %q", resp.Error.Description)
}
}
cfg.RePublish = &RePublish{
Source: "foo.>",
Destination: "foo.>",
}
expectFail()
cfg.RePublish = &RePublish{
Source: "bar.bar",
Destination: "foo.bar",
}
expectFail()
cfg.RePublish = &RePublish{
Source: "baz",
Destination: "bar.bar",
}
expectFail()
}
func TestJetStreamStreamRepublishOneTokenMatch(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Do by hand for now.
cfg := &StreamConfig{
Name: "Stream1",
Storage: MemoryStorage,
Subjects: []string{"one", "four"},
RePublish: &RePublish{
Source: "one",
Destination: "uno",
HeadersOnly: false,
},
}
addStream(t, nc, cfg)
sub, err := nc.SubscribeSync("uno")
require_NoError(t, err)
msg, toSend := bytes.Repeat([]byte("Z"), 512), 100
for i := 0; i < toSend; i++ {
js.PublishAsync("one", msg)
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
checkSubsPending(t, sub, toSend)
m, err := sub.NextMsg(time.Second)
require_NoError(t, err)
if !(len(m.Data) > 0) {
t.Fatalf("Expected msg data")
}
}
func TestJetStreamStreamRepublishSuppressesInvalidSubject(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
addStream(t, nc, &StreamConfig{
Name: "Stream1",
Storage: MemoryStorage,
Subjects: []string{"one"},
RePublish: &RePublish{
Source: "one",
Destination: "uno\r\nPUB hijack.subject 8\r\nhijacked!\r\n",
},
})
hijackSub, err := nc.SubscribeSync("hijack.subject")
require_NoError(t, err)
repubSub, err := nc.SubscribeSync("uno")
require_NoError(t, err)
require_NoError(t, nc.Flush())
_, err = js.Publish("one", []byte("payload"))
require_NoError(t, err)
// Should receive nothing on either the hijack or republish subjects.
_, err = hijackSub.NextMsg(250 * time.Millisecond)
require_Error(t, err)
_, err = repubSub.NextMsg(250 * time.Millisecond)
require_Error(t, err)
}
func TestJetStreamStreamRepublishLeftRightMappingFunctions(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
for i, dest := range []string{"{{left(1,3)}}", "{{right(1,3)}}"} {
t.Run(dest, func(t *testing.T) {
inSubj := fmt.Sprintf("in%d.*", i)
addStream(t, nc, &StreamConfig{
Name: fmt.Sprintf("S%d", i),
Storage: MemoryStorage,
Subjects: []string{inSubj},
RePublish: &RePublish{
Source: inSubj,
Destination: dest,
},
})
sub, err := nc.SubscribeSync(">")
require_NoError(t, err)
require_NoError(t, nc.Flush())
_, err = js.Publish(fmt.Sprintf("in%d.abcdef", i), []byte("x"))
require_NoError(t, err)
// We should receive both the original ("in.abcdef") and the republished message.
gotRepub := false
checkFor(t, time.Second, 10*time.Millisecond, func() error {
msg, err := sub.NextMsg(100 * time.Millisecond)
if err != nil {
return err
}
if msg.Subject != fmt.Sprintf("in%d.abcdef", i) {
gotRepub = true
return nil
}
return fmt.Errorf("waiting for republished message")
})
require_True(t, gotRepub)
})
}
}
func TestJetStreamStreamRepublishMultiTokenMatch(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Do by hand for now.
cfg := &StreamConfig{
Name: "Stream1",
Storage: MemoryStorage,
Subjects: []string{"one.>", "four.>"},
RePublish: &RePublish{
Source: "one.two.>",
Destination: "uno.dos.>",
HeadersOnly: false,
},
}
addStream(t, nc, cfg)
sub, err := nc.SubscribeSync("uno.dos.>")
require_NoError(t, err)
msg, toSend := bytes.Repeat([]byte("Z"), 512), 100
for i := 0; i < toSend; i++ {
js.PublishAsync("one.two.three", msg)
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
checkSubsPending(t, sub, toSend)
m, err := sub.NextMsg(time.Second)
require_NoError(t, err)
if !(len(m.Data) > 0) {
t.Fatalf("Expected msg data")
}
}
func TestJetStreamStreamRepublishAnySubjectMatch(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Do by hand for now.
cfg := &StreamConfig{
Name: "Stream1",
Storage: MemoryStorage,
Subjects: []string{"one.>", "four.>"},
RePublish: &RePublish{
Destination: "uno.dos.>",
HeadersOnly: false,
},
}
addStream(t, nc, cfg)
sub, err := nc.SubscribeSync("uno.dos.>")
require_NoError(t, err)
msg, toSend := bytes.Repeat([]byte("Z"), 512), 100
for i := 0; i < toSend; i++ {
js.PublishAsync("one.two.three", msg)
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
checkSubsPending(t, sub, toSend)
m, err := sub.NextMsg(time.Second)
require_NoError(t, err)
if !(len(m.Data) > 0) {
t.Fatalf("Expected msg data")
}
}
func TestJetStreamStreamRepublishMultiTokenNoMatch(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Do by hand for now.
cfg := &StreamConfig{
Name: "Stream1",
Storage: MemoryStorage,
Subjects: []string{"one.>", "four.>"},
RePublish: &RePublish{
Source: "one.two.>",
Destination: "uno.dos.>",
HeadersOnly: true,
},
}
addStream(t, nc, cfg)
sub, err := nc.SubscribeSync("uno.dos.>")
require_NoError(t, err)
msg, toSend := bytes.Repeat([]byte("Z"), 512), 100
for i := 0; i < toSend; i++ {
js.PublishAsync("four.five.six", msg)
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
checkSubsPending(t, sub, 0)
require_NoError(t, err)
}
func TestJetStreamStreamRepublishOneTokenNoMatch(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Do by hand for now.
cfg := &StreamConfig{
Name: "Stream1",
Storage: MemoryStorage,
Subjects: []string{"one", "four"},
RePublish: &RePublish{
Source: "one",
Destination: "uno",
HeadersOnly: true,
},
}
addStream(t, nc, cfg)
sub, err := nc.SubscribeSync("uno")
require_NoError(t, err)
msg, toSend := bytes.Repeat([]byte("Z"), 512), 100
for i := 0; i < toSend; i++ {
js.PublishAsync("four", msg)
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
checkSubsPending(t, sub, 0)
require_NoError(t, err)
}
func TestJetStreamStreamRepublishHeadersOnly(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Do by hand for now.
cfg := &StreamConfig{
Name: "RPC",
Storage: MemoryStorage,
Subjects: []string{"foo", "bar", "baz"},
RePublish: &RePublish{
Destination: "RP.>",
HeadersOnly: true,
},
}
addStream(t, nc, cfg)
sub, err := nc.SubscribeSync("RP.>")
require_NoError(t, err)
msg, toSend := bytes.Repeat([]byte("Z"), 512), 100
for i := 0; i < toSend; i++ {
js.PublishAsync("foo", msg)
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
checkSubsPending(t, sub, toSend)
last := "0"
for i := 0; i < toSend; i++ {
m, err := sub.NextMsg(time.Second)
require_NoError(t, err)
if len(m.Data) > 0 {
t.Fatalf("Expected no msg just headers, but got %d bytes", len(m.Data))
}
if sz := m.Header.Get(JSMsgSize); sz != "512" {
t.Fatalf("Expected msg size hdr, got %q", sz)
}
if lastHeader := m.Header.Get(JSLastSequence); lastHeader != last {
t.Fatalf("Expected last message header %q, got %q", last, lastHeader)
}
last = m.Header.Get(JSSequence)
}
}
func TestJetStreamMsgGetNoAdvisory(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "foo"})
require_NoError(t, err)
for i := 0; i < 100; i++ {
js.PublishAsync("foo", []byte("ok"))
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
sub, err := nc.SubscribeSync("$JS.EVENT.ADVISORY.>")
require_NoError(t, err)
_, err = js.GetMsg("foo", 1)
require_NoError(t, err)
checkSubsPending(t, sub, 0)
}
func TestJetStreamDirectMsgGet(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
// Do by hand for now.
cfg := &StreamConfig{
Name: "DSMG",
Storage: MemoryStorage,
Subjects: []string{"foo", "bar", "baz"},
MaxMsgsPer: 1,
AllowDirect: true,
}
addStream(t, nc, cfg)
sendStreamMsg(t, nc, "foo", "foo")
sendStreamMsg(t, nc, "bar", "bar")
sendStreamMsg(t, nc, "baz", "baz")
getSubj := fmt.Sprintf(JSDirectMsgGetT, "DSMG")
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) == "foo")
require_True(t, m.Header.Get(JSStream) == "DSMG")
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_)
m = getMsg(&JSApiMsgGetRequest{LastFor: "bar"})
require_True(t, string(m.Data) == "bar")
require_True(t, m.Header.Get(JSStream) == "DSMG")
require_True(t, m.Header.Get(JSSequence) == "2")
require_True(t, m.Header.Get(JSSubject) == "bar")
require_True(t, m.Subject != "bar")
require_True(t, m.Header.Get(JSTimeStamp) != _EMPTY_)
m = getMsg(&JSApiMsgGetRequest{LastFor: "baz"})
require_True(t, string(m.Data) == "baz")
require_True(t, m.Header.Get(JSStream) == "DSMG")
require_True(t, m.Header.Get(JSSequence) == "3")
require_True(t, m.Header.Get(JSSubject) == "baz")
require_True(t, m.Subject != "baz")
require_True(t, m.Header.Get(JSTimeStamp) != _EMPTY_)
// Test error conditions
// Nil request
m = getMsg(nil)
require_True(t, len(m.Data) == 0)
require_True(t, m.Header.Get("Status") == "408")
require_True(t, m.Header.Get("Description") == "Empty Request")
// Empty request
m = getMsg(&JSApiMsgGetRequest{})
require_True(t, len(m.Data) == 0)
require_True(t, m.Header.Get("Status") == "408")
require_True(t, m.Header.Get("Description") == "Empty Request")
// Both set
m = getMsg(&JSApiMsgGetRequest{Seq: 1, LastFor: "foo"})
require_True(t, len(m.Data) == 0)
require_True(t, m.Header.Get("Status") == "408")
require_True(t, m.Header.Get("Description") == "Bad Request")
// Not found
m = getMsg(&JSApiMsgGetRequest{LastFor: "foobar"})
require_True(t, len(m.Data) == 0)
require_True(t, m.Header.Get("Status") == "404")
require_True(t, m.Header.Get("Description") == "Message Not Found")
m = getMsg(&JSApiMsgGetRequest{Seq: 22})
require_True(t, len(m.Data) == 0)
require_True(t, m.Header.Get("Status") == "404")
require_True(t, m.Header.Get("Description") == "Message Not Found")
}
// This allows support for a get next given a sequence as a starting.
// This allows these to be chained together if needed for sparse streams.
func TestJetStreamDirectMsgGetNext(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
// Do by hand for now.
cfg := &StreamConfig{
Name: "DSMG",
Storage: MemoryStorage,
Subjects: []string{"foo", "bar", "baz"},
AllowDirect: true,
}
addStream(t, nc, cfg)
sendStreamMsg(t, nc, "foo", "foo")
for i := 0; i < 10; i++ {
sendStreamMsg(t, nc, "bar", "bar")
}
for i := 0; i < 10; i++ {
sendStreamMsg(t, nc, "baz", "baz")
}
sendStreamMsg(t, nc, "foo", "foo")
getSubj := fmt.Sprintf(JSDirectMsgGetT, "DSMG")
getMsg := func(seq uint64, subj string) *nats.Msg {
req := []byte(fmt.Sprintf(`{"seq": %d, "next_by_subj": %q}`, seq, subj))
m, err := nc.Request(getSubj, req, time.Second)
require_NoError(t, err)
return m
}
m := getMsg(0, "foo")
require_True(t, m.Header.Get(JSSequence) == "1")
require_True(t, m.Header.Get(JSSubject) == "foo")
m = getMsg(1, "foo")
require_True(t, m.Header.Get(JSSequence) == "1")
require_True(t, m.Header.Get(JSSubject) == "foo")
m = getMsg(2, "foo")
require_True(t, m.Header.Get(JSSequence) == "22")
require_True(t, m.Header.Get(JSSubject) == "foo")
m = getMsg(2, "bar")
require_True(t, m.Header.Get(JSSequence) == "2")
require_True(t, m.Header.Get(JSSubject) == "bar")
m = getMsg(5, "baz")
require_True(t, m.Header.Get(JSSequence) == "12")
require_True(t, m.Header.Get(JSSubject) == "baz")
m = getMsg(14, "baz")
require_True(t, m.Header.Get(JSSequence) == "14")
require_True(t, m.Header.Get(JSSubject) == "baz")
}
///////////////////////////////////////////////////////////////////////////
// Simple JetStream Benchmarks
///////////////////////////////////////////////////////////////////////////
func Benchmark__JetStreamPubWithAck(b *testing.B) {
s := RunBasicJetStreamServer(b)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: "foo"})
if err != nil {
b.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, err := nats.Connect(s.ClientURL())
if err != nil {
b.Fatalf("Failed to create client: %v", err)
}
defer nc.Close()
b.ResetTimer()
for i := 0; i < b.N; i++ {
nc.Request("foo", []byte("Hello World!"), 50*time.Millisecond)
}
b.StopTimer()
state := mset.state()
if int(state.Msgs) != b.N {
b.Fatalf("Expected %d messages, got %d", b.N, state.Msgs)
}
}
func Benchmark____JetStreamPubNoAck(b *testing.B) {
s := RunBasicJetStreamServer(b)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: "foo"})
if err != nil {
b.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, err := nats.Connect(s.ClientURL())
if err != nil {
b.Fatalf("Failed to create client: %v", err)
}
defer nc.Close()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := nc.Publish("foo", []byte("Hello World!")); err != nil {
b.Fatalf("Unexpected error: %v", err)
}
}
nc.Flush()
b.StopTimer()
state := mset.state()
if int(state.Msgs) != b.N {
b.Fatalf("Expected %d messages, got %d", b.N, state.Msgs)
}
}
func Benchmark_JetStreamPubAsyncAck(b *testing.B) {
s := RunBasicJetStreamServer(b)
defer s.Shutdown()
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: "foo"})
if err != nil {
b.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, err := nats.Connect(s.ClientURL(), nats.NoReconnect())
if err != nil {
b.Fatalf("Failed to create client: %v", err)
}
defer nc.Close()
// Put ack stream on its own connection.
anc, err := nats.Connect(s.ClientURL())
if err != nil {
b.Fatalf("Failed to create client: %v", err)
}
defer anc.Close()
acks := nats.NewInbox()
sub, _ := anc.Subscribe(acks, func(m *nats.Msg) {
// Just eat them for this test.
})
// set max pending to unlimited.
sub.SetPendingLimits(-1, -1)
defer sub.Unsubscribe()
anc.Flush()
runtime.GC()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := nc.PublishRequest("foo", acks, []byte("Hello World!")); err != nil {
b.Fatalf("[%d] Unexpected error: %v", i, err)
}
}
nc.Flush()
b.StopTimer()
state := mset.state()
if int(state.Msgs) != b.N {
b.Fatalf("Expected %d messages, got %d", b.N, state.Msgs)
}
}
func Benchmark____JetStreamSubNoAck(b *testing.B) {
if b.N < 10000 {
return
}
s := RunBasicJetStreamServer(b)
defer s.Shutdown()
mname := "foo"
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: mname})
if err != nil {
b.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, err := nats.Connect(s.ClientURL(), nats.NoReconnect())
if err != nil {
b.Fatalf("Failed to create client: %v", err)
}
defer nc.Close()
// Queue up messages.
for i := 0; i < b.N; i++ {
nc.Publish(mname, []byte("Hello World!"))
}
nc.Flush()
state := mset.state()
if state.Msgs != uint64(b.N) {
b.Fatalf("Expected %d messages, got %d", b.N, state.Msgs)
}
total := int32(b.N)
received := int32(0)
done := make(chan bool)
deliverTo := "DM"
oname := "O"
nc.Subscribe(deliverTo, func(m *nats.Msg) {
// We only are done when we receive all, we could check for gaps too.
if atomic.AddInt32(&received, 1) >= total {
done <- true
}
})
nc.Flush()
b.ResetTimer()
o, err := mset.addConsumer(&ConsumerConfig{DeliverSubject: deliverTo, Durable: oname, AckPolicy: AckNone})
if err != nil {
b.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
<-done
b.StopTimer()
}
func benchJetStreamWorkersAndBatch(b *testing.B, numWorkers, batchSize int) {
// Avoid running at too low of numbers since that chews up memory and GC.
if b.N < numWorkers*batchSize {
return
}
s := RunBasicJetStreamServer(b)
defer s.Shutdown()
mname := "MSET22"
mset, err := s.GlobalAccount().addStream(&StreamConfig{Name: mname})
if err != nil {
b.Fatalf("Unexpected error adding stream: %v", err)
}
defer mset.delete()
nc, err := nats.Connect(s.ClientURL(), nats.NoReconnect())
if err != nil {
b.Fatalf("Failed to create client: %v", err)
}
defer nc.Close()
// Queue up messages.
for i := 0; i < b.N; i++ {
nc.Publish(mname, []byte("Hello World!"))
}
nc.Flush()
state := mset.state()
if state.Msgs != uint64(b.N) {
b.Fatalf("Expected %d messages, got %d", b.N, state.Msgs)
}
// Create basic work queue mode consumer.
oname := "WQ"
o, err := mset.addConsumer(&ConsumerConfig{Durable: oname, AckPolicy: AckExplicit})
if err != nil {
b.Fatalf("Expected no error with registered interest, got %v", err)
}
defer o.delete()
total := int32(b.N)
received := int32(0)
start := make(chan bool)
done := make(chan bool)
batchSizeMsg := []byte(strconv.Itoa(batchSize))
reqNextMsgSubj := o.requestNextMsgSubject()
for i := 0; i < numWorkers; i++ {
nc, err := nats.Connect(s.ClientURL(), nats.NoReconnect())
if err != nil {
b.Fatalf("Failed to create client: %v", err)
}
defer nc.Close()
deliverTo := nats.NewInbox()
nc.Subscribe(deliverTo, func(m *nats.Msg) {
if atomic.AddInt32(&received, 1) >= total {
done <- true
}
// Ack + Next request.
nc.PublishRequest(m.Reply, deliverTo, AckNext)
})
nc.Flush()
go func() {
<-start
nc.PublishRequest(reqNextMsgSubj, deliverTo, batchSizeMsg)
}()
}
b.ResetTimer()
close(start)
<-done
b.StopTimer()
}
func Benchmark___JetStream1x1Worker(b *testing.B) {
benchJetStreamWorkersAndBatch(b, 1, 1)
}
func Benchmark__JetStream1x1kWorker(b *testing.B) {
benchJetStreamWorkersAndBatch(b, 1, 1024)
}
func Benchmark_JetStream10x1kWorker(b *testing.B) {
benchJetStreamWorkersAndBatch(b, 10, 1024)
}
func Benchmark_JetStream4x512Worker(b *testing.B) {
benchJetStreamWorkersAndBatch(b, 4, 512)
}
func TestJetStreamKVMemoryStorePerf(t *testing.T) {
// Comment out to run, holding place for now.
t.SkipNow()
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
kv, err := js.CreateKeyValue(&nats.KeyValueConfig{Bucket: "TEST", History: 1, Storage: nats.MemoryStorage})
require_NoError(t, err)
start := time.Now()
for i := 0; i < 100_000; i++ {
_, err := kv.PutString(fmt.Sprintf("foo.%d", i), "HELLO")
require_NoError(t, err)
}
fmt.Printf("Took %v for first run\n", time.Since(start))
start = time.Now()
for i := 0; i < 100_000; i++ {
_, err := kv.PutString(fmt.Sprintf("foo.%d", i), "HELLO WORLD")
require_NoError(t, err)
}
fmt.Printf("Took %v for second run\n", time.Since(start))
start = time.Now()
for i := 0; i < 100_000; i++ {
_, err := kv.Get(fmt.Sprintf("foo.%d", i))
require_NoError(t, err)
}
fmt.Printf("Took %v for get\n", time.Since(start))
}
func TestJetStreamKVMemoryStoreDirectGetPerf(t *testing.T) {
// Comment out to run, holding place for now.
t.SkipNow()
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &StreamConfig{
Name: "TEST",
Storage: MemoryStorage,
Subjects: []string{"foo.*"},
MaxMsgsPer: 1,
AllowDirect: true,
}
addStream(t, nc, cfg)
start := time.Now()
for i := 0; i < 100_000; i++ {
_, err := js.Publish(fmt.Sprintf("foo.%d", i), []byte("HELLO"))
require_NoError(t, err)
}
fmt.Printf("Took %v for put\n", time.Since(start))
getSubj := fmt.Sprintf(JSDirectMsgGetT, "TEST")
const tmpl = "{\"last_by_subj\":%q}"
start = time.Now()
for i := 0; i < 100_000; i++ {
req := []byte(fmt.Sprintf(tmpl, fmt.Sprintf("foo.%d", i)))
_, err := nc.Request(getSubj, req, time.Second)
require_NoError(t, err)
}
fmt.Printf("Took %v for get\n", time.Since(start))
}
func TestJetStreamMultiplePullPerf(t *testing.T) {
skip(t)
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
js.AddStream(&nats.StreamConfig{Name: "mp22", Storage: nats.FileStorage})
defer js.DeleteStream("mp22")
n, msg := 1_000_000, []byte("OK")
for i := 0; i < n; i++ {
js.PublishAsync("mp22", msg)
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(10 * time.Second):
t.Fatalf("Did not receive completion signal")
}
si, err := js.StreamInfo("mp22")
require_NoError(t, err)
fmt.Printf("msgs: %d, total_bytes: %v\n", si.State.Msgs, friendlyBytes(int64(si.State.Bytes)))
// 10 pull subscribers each asking for 100 msgs.
_, err = js.AddConsumer("mp22", &nats.ConsumerConfig{
Durable: "d",
MaxAckPending: 8_000,
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
startCh := make(chan bool)
var wg sync.WaitGroup
np, bs := 10, 100
count := 0
for i := 0; i < np; i++ {
nc, js := jsClientConnect(t, s)
defer nc.Close()
sub, err := js.PullSubscribe("mp22", "d")
require_NoError(t, err)
wg.Add(1)
go func(sub *nats.Subscription) {
defer wg.Done()
<-startCh
for i := 0; i < n/(np*bs); i++ {
msgs, err := sub.Fetch(bs)
if err != nil {
t.Logf("Got error on pull: %v", err)
return
}
if len(msgs) != bs {
t.Logf("Expected %d msgs, got %d", bs, len(msgs))
return
}
count += len(msgs)
for _, m := range msgs {
m.Ack()
}
}
}(sub)
}
start := time.Now()
close(startCh)
wg.Wait()
tt := time.Since(start)
fmt.Printf("Took %v to receive %d msgs [%d]\n", tt, n, count)
fmt.Printf("%.0f msgs/s\n", float64(n)/tt.Seconds())
fmt.Printf("%.0f mb/s\n\n", float64(si.State.Bytes/(1024*1024))/tt.Seconds())
}
func TestJetStreamMirrorUpdatesNotSupported(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "SOURCE"})
require_NoError(t, err)
cfg := &nats.StreamConfig{
Name: "M",
Mirror: &nats.StreamSource{Name: "SOURCE"},
}
_, err = js.AddStream(cfg)
require_NoError(t, err)
// Promoting a mirror is supported though.
cfg.Mirror = nil
_, err = js.UpdateStream(cfg)
require_NoError(t, err)
}
func TestJetStreamMirrorFirstSeqNotSupported(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
_, err := s.gacc.addStream(&StreamConfig{Name: "SOURCE"})
require_NoError(t, err)
cfg := &StreamConfig{
Name: "M",
Mirror: &StreamSource{Name: "SOURCE"},
FirstSeq: 123,
}
_, err = s.gacc.addStream(cfg)
require_Error(t, err, NewJSMirrorWithFirstSeqError())
}
func TestJetStreamDirectGetBySubject(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 64GB, max_file_store: 10TB, store_dir: %q}
ONLYME = {
publish = { allow = "$JS.API.DIRECT.GET.KV.vid.22.>"}
}
accounts: {
A: {
jetstream: enabled
users: [
{ user: admin, password: s3cr3t },
{ user: user, password: pwd, permissions: $ONLYME},
]
},
}
`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
nc, js := jsClientConnect(t, s, nats.UserInfo("admin", "s3cr3t"))
defer nc.Close()
// Do by hand for now.
cfg := &StreamConfig{
Name: "KV",
Storage: MemoryStorage,
Subjects: []string{"vid.*.>"},
MaxMsgsPer: 1,
AllowDirect: true,
}
addStream(t, nc, cfg)
// Add in mirror as well.
cfg = &StreamConfig{
Name: "M",
Storage: MemoryStorage,
Mirror: &StreamSource{Name: "KV"},
MirrorDirect: true,
}
addStream(t, nc, cfg)
v22 := "vid.22.speed"
v33 := "vid.33.speed"
_, err := js.Publish(v22, []byte("100"))
require_NoError(t, err)
_, err = js.Publish(v33, []byte("55"))
require_NoError(t, err)
// User the restricted user.
nc, _ = jsClientConnect(t, s, nats.UserInfo("user", "pwd"))
defer nc.Close()
errCh := make(chan error, 10)
nc.SetErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, e error) {
select {
case errCh <- e:
default:
}
})
getSubj := fmt.Sprintf(JSDirectGetLastBySubjectT, "KV", v22)
m, err := nc.Request(getSubj, nil, time.Second)
require_NoError(t, err)
require_True(t, string(m.Data) == "100")
// Now attempt to access vid 33 data..
getSubj = fmt.Sprintf(JSDirectGetLastBySubjectT, "KV", v33)
_, err = nc.Request(getSubj, nil, 200*time.Millisecond)
require_Error(t, err) // timeout here.
select {
case e := <-errCh:
if !strings.HasPrefix(e.Error(), "nats: permissions violation") {
t.Fatalf("Expected a permissions violation but got %v", e)
}
case <-time.After(time.Second):
t.Fatalf("Expected to get a permissions error, got none")
}
// Now make sure mirrors are doing right thing with new way as well.
var sawMirror bool
getSubj = fmt.Sprintf(JSDirectGetLastBySubjectT, "KV", v22)
for i := 0; i < 100; i++ {
m, err := nc.Request(getSubj, nil, time.Second)
require_NoError(t, err)
if shdr := m.Header.Get(JSStream); shdr == "M" {
sawMirror = true
break
}
}
if !sawMirror {
t.Fatalf("Expected to see the mirror respond at least once")
}
}
func TestJetStreamProperErrorDueToOverlapSubjects(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
test := func(t *testing.T, s *Server) {
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
})
require_NoError(t, err)
// Now do this by end since we want to check the error returned.
sc := &nats.StreamConfig{
Name: "TEST2",
Subjects: []string{"foo.>"},
}
req, _ := json.Marshal(sc)
msg, err := nc.Request(fmt.Sprintf(JSApiStreamCreateT, sc.Name), req, time.Second)
require_NoError(t, err)
var scResp JSApiStreamCreateResponse
err = json.Unmarshal(msg.Data, &scResp)
require_NoError(t, err)
if scResp.Error == nil || !IsNatsErr(scResp.Error, JSStreamSubjectOverlapErr) {
t.Fatalf("Did not receive correct error: %+v", scResp)
}
}
t.Run("standalone", func(t *testing.T) { test(t, s) })
t.Run("clustered", func(t *testing.T) { test(t, c.randomServer()) })
}
func TestJetStreamServerCipherConvert(t *testing.T) {
tmpl := `
server_name: S22
listen: 127.0.0.1:-1
jetstream: {key: s3cr3t, store_dir: '%s', cipher: %s}
`
storeDir := t.TempDir()
// Create a stream and a consumer under one cipher, and restart the server with a new cipher.
conf := createConfFile(t, []byte(fmt.Sprintf(tmpl, storeDir, "AES")))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
// Client based API
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
}
if _, err := js.AddStream(cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for i := 0; i < 1000; i++ {
msg := []byte(fmt.Sprintf("TOP SECRET DOCUMENT #%d", i+1))
_, err := js.Publish("foo", msg)
require_NoError(t, err)
}
// Make sure consumers convert as well.
sub, err := js.PullSubscribe("foo", "dlc")
require_NoError(t, err)
for _, m := range fetchMsgs(t, sub, 100, 5*time.Second) {
m.AckSync()
}
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
ci, err := js.ConsumerInfo("TEST", "dlc")
require_NoError(t, err)
// Stop current
s.Shutdown()
conf = createConfFile(t, []byte(fmt.Sprintf(tmpl, storeDir, "ChaCha")))
s, _ = RunServerWithConfig(conf)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
si2, err := js.StreamInfo("TEST")
require_NoError(t, err)
if !reflect.DeepEqual(si, si2) {
t.Fatalf("Stream infos did not match\n%+v\nvs\n%+v", si, si2)
}
ci2, err := js.ConsumerInfo("TEST", "dlc")
require_NoError(t, err)
// Consumer create times can be slightly off after restore from disk.
now := time.Now()
ci.Created, ci2.Created = now, now
ci.Delivered.Last, ci2.Delivered.Last = nil, nil
ci.AckFloor.Last, ci2.AckFloor.Last = nil, nil
// Also clusters will be different.
ci.Cluster, ci2.Cluster = nil, nil
if !reflect.DeepEqual(ci, ci2) {
t.Fatalf("Consumer infos did not match\n%+v\nvs\n%+v", ci, ci2)
}
}
func TestJetStreamAllowDirectAfterUpdate(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"*"},
})
require_NoError(t, err)
sendStreamMsg(t, nc, "foo", "msg")
si, err := js.UpdateStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"*"},
AllowDirect: true,
})
require_NoError(t, err)
require_True(t, si.Config.AllowDirect)
_, err = js.GetLastMsg("TEST", "foo", nats.DirectGet(), nats.MaxWait(100*time.Millisecond))
require_NoError(t, err)
// Make sure turning off works too.
si, err = js.UpdateStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"*"},
AllowDirect: false,
})
require_NoError(t, err)
require_False(t, si.Config.AllowDirect)
_, err = js.GetLastMsg("TEST", "foo", nats.DirectGet(), nats.MaxWait(100*time.Millisecond))
require_Error(t, err)
}
func TestJetStreamSubjectBasedFilteredConsumers(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {max_mem_store: 64GB, max_file_store: 10TB, store_dir: %q}
accounts: {
A: {
jetstream: enabled
users: [ {
user: u,
password: p
permissions {
publish {
allow: [
'ID.>',
'$JS.API.INFO',
'$JS.API.STREAM.>',
'$JS.API.CONSUMER.INFO.>',
'$JS.API.CONSUMER.CREATE.TEST.VIN-xxx.ID.foo.>', # Only allow ID.foo.
]
deny: [ '$JS.API.CONSUMER.CREATE.*', '$JS.API.CONSUMER.DURABLE.CREATE.*.*']
}
}
} ]
},
}
`, t.TempDir())))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
nc, js := jsClientConnect(t, s, nats.UserInfo("u", "p"), nats.ErrorHandler(noOpErrHandler))
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"ID.*.*"},
})
require_NoError(t, err)
for i := 0; i < 100; i++ {
js.Publish(fmt.Sprintf("ID.foo.%d", i*3), nil)
js.Publish(fmt.Sprintf("ID.bar.%d", i*3+1), nil)
js.Publish(fmt.Sprintf("ID.baz.%d", i*3+2), nil)
}
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_True(t, si.State.Msgs == 300)
// Trying to create a consumer with non filtered API should fail.
js, err = nc.JetStream(nats.MaxWait(200 * time.Millisecond))
require_NoError(t, err)
_, err = js.SubscribeSync("ID.foo.*")
require_Error(t, err, nats.ErrTimeout, context.DeadlineExceeded)
_, err = js.SubscribeSync("ID.foo.*", nats.Durable("dlc"))
require_Error(t, err, nats.ErrTimeout, context.DeadlineExceeded)
// Direct filtered should work.
// Need to do by hand for now.
ecSubj := fmt.Sprintf(JSApiConsumerCreateExT, "TEST", "VIN-xxx", "ID.foo.*")
crReq := CreateConsumerRequest{
Stream: "TEST",
Config: ConsumerConfig{
DeliverPolicy: DeliverLast,
FilterSubject: "ID.foo.*",
AckPolicy: AckExplicit,
},
}
req, err := json.Marshal(crReq)
require_NoError(t, err)
resp, err := nc.Request(ecSubj, req, 500*time.Millisecond)
require_NoError(t, err)
var ccResp JSApiConsumerCreateResponse
err = json.Unmarshal(resp.Data, &ccResp)
require_NoError(t, err)
if ccResp.Error != nil {
t.Fatalf("Unexpected error: %v", ccResp.Error)
}
cfg := ccResp.Config
ci := ccResp.ConsumerInfo
// Make sure we recognized as an ephemeral (since no durable was set) and that we have an InactiveThreshold.
// Make sure we captured preferred ephemeral name.
if ci.Name != "VIN-xxx" {
t.Fatalf("Did not get correct name, expected %q got %q", "xxx", ci.Name)
}
if cfg.InactiveThreshold == 0 {
t.Fatalf("Expected default inactive threshold to be set, got %v", cfg.InactiveThreshold)
}
// Make sure we can not use different consumer name since locked above.
ecSubj = fmt.Sprintf(JSApiConsumerCreateExT, "TEST", "VIN-zzz", "ID.foo.*")
_, err = nc.Request(ecSubj, req, 500*time.Millisecond)
require_Error(t, err, nats.ErrTimeout)
// Now check that we error when we mismatch filtersubject.
crReq = CreateConsumerRequest{
Stream: "TEST",
Config: ConsumerConfig{
DeliverPolicy: DeliverLast,
FilterSubject: "ID.bar.*",
AckPolicy: AckExplicit,
},
}
req, err = json.Marshal(crReq)
require_NoError(t, err)
ecSubj = fmt.Sprintf(JSApiConsumerCreateExT, "TEST", "VIN-xxx", "ID.foo.*")
resp, err = nc.Request(ecSubj, req, 500*time.Millisecond)
require_NoError(t, err)
err = json.Unmarshal(resp.Data, &ccResp)
require_NoError(t, err)
checkNatsError(t, ccResp.Error, JSConsumerCreateFilterSubjectMismatchErr)
// Now make sure if we change subject to match that we can not create a filtered consumer on ID.bar.>
ecSubj = fmt.Sprintf(JSApiConsumerCreateExT, "TEST", "VIN-xxx", "ID.bar.*")
_, err = nc.Request(ecSubj, req, 500*time.Millisecond)
require_Error(t, err, nats.ErrTimeout)
}
func TestJetStreamStreamSubjectsOverlap(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*", "foo.A"},
})
require_Error(t, err)
require_True(t, strings.Contains(err.Error(), "overlaps"))
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
})
require_NoError(t, err)
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*", "foo.A"},
})
require_Error(t, err)
require_True(t, strings.Contains(err.Error(), "overlaps"))
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.bar.*", "foo.*.bar"},
})
require_Error(t, err)
require_True(t, strings.Contains(err.Error(), "overlaps"))
}
func TestJetStreamStreamTransformOverlap(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>"},
})
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{
Name: "MIRROR",
Mirror: &nats.StreamSource{Name: "TEST",
SubjectTransforms: []nats.SubjectTransformConfig{
{
Source: "foo.*.bar",
Destination: "baz",
},
{
Source: "foo.bar.*",
Destination: "baz",
},
},
}})
require_Error(t, err)
require_True(t, strings.Contains(err.Error(), "overlap"))
}
func TestJetStreamSuppressAllowDirect(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
si, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"key.*"},
MaxMsgsPerSubject: 1,
AllowDirect: true,
})
require_NoError(t, err)
require_True(t, si.Config.AllowDirect)
si, err = js.UpdateStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"key.*"},
MaxMsgsPerSubject: 1,
AllowDirect: false,
})
require_NoError(t, err)
require_False(t, si.Config.AllowDirect)
sendStreamMsg(t, nc, "key.22", "msg")
_, err = js.GetLastMsg("TEST", "foo", nats.DirectGet(), nats.MaxWait(100*time.Millisecond))
require_Error(t, err)
}
func TestJetStreamAccountPurge(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)
storeDir := t.TempDir()
cfg := createConfFile(t, []byte(fmt.Sprintf(`
host: 127.0.0.1
port:-1
server_name: S1
operator: %s
system_account: %s
resolver: {
type: full
dir: '%s/jwt'
}
jetstream: {max_mem_store: 256MB, max_file_store: 2GB, store_dir: '%s/js'}
`, ojwt, syspub, storeDir, storeDir)))
defer os.Remove(cfg)
s, o := RunServerWithConfig(cfg)
updateJwt(t, s.ClientURL(), sysCreds, sysJwt, 1)
updateJwt(t, s.ClientURL(), sysCreds, accJwt, 1)
defer s.Shutdown()
inspectDirs := func(t *testing.T, accTotal int) error {
t.Helper()
if accTotal == 0 {
files, err := os.ReadDir(filepath.Join(o.StoreDir, "jetstream", accpub))
require_True(t, len(files) == accTotal || err != nil)
} else {
files, err := os.ReadDir(filepath.Join(o.StoreDir, "jetstream", accpub, "streams"))
require_NoError(t, err)
require_True(t, len(files) == accTotal)
}
return nil
}
createTestData := func() {
nc := natsConnect(t, s.ClientURL(), nats.UserCredentials(accCreds))
defer nc.Close()
js, err := nc.JetStream()
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST1",
Subjects: []string{"foo"},
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST1",
&nats.ConsumerConfig{Durable: "DUR1",
AckPolicy: nats.AckExplicitPolicy})
require_NoError(t, err)
}
purge := func(t *testing.T) {
t.Helper()
var resp JSApiAccountPurgeResponse
ncsys := natsConnect(t, s.ClientURL(), nats.UserCredentials(sysCreds))
defer ncsys.Close()
m, err := ncsys.Request(fmt.Sprintf(JSApiAccountPurgeT, accpub), nil, 5*time.Second)
require_NoError(t, err)
err = json.Unmarshal(m.Data, &resp)
require_NoError(t, err)
require_True(t, resp.Initiated)
}
createTestData()
inspectDirs(t, 1)
purge(t)
inspectDirs(t, 0)
createTestData()
inspectDirs(t, 1)
// A request with path separators should be rejected.
var resp JSApiAccountPurgeResponse
ncsys := natsConnect(t, s.ClientURL(), nats.UserCredentials(sysCreds))
defer ncsys.Close()
m, err := ncsys.Request(fmt.Sprintf(JSApiAccountPurgeT, "foo/hello"), nil, 5*time.Second)
require_NoError(t, err)
err = json.Unmarshal(m.Data, &resp)
require_NoError(t, err)
require_True(t, resp.Error != nil)
require_Error(t, resp.Error, NewJSStreamGeneralError(errors.New("account name can not contain path separators")))
s.Shutdown()
require_NoError(t, os.Remove(storeDir+"/jwt/"+accpub+".jwt"))
s, o = RunServerWithConfig(o.ConfigFile)
defer s.Shutdown()
inspectDirs(t, 1)
purge(t)
inspectDirs(t, 0)
}
// For issue https://github.com/nats-io/nats-server/issues/3612
// Do auto cleanup.
func TestJetStreamDanglingMessageAutoCleanup(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Retention: nats.InterestPolicy,
})
require_NoError(t, err)
sub, err := js.PullSubscribe("foo", "dlc", nats.MaxAckPending(10))
require_NoError(t, err)
// Send 100 msgs
n := 100
for i := 0; i < n; i++ {
sendStreamMsg(t, nc, "foo", "msg")
}
// Grab and ack 10 messages.
for _, m := range fetchMsgs(t, sub, 10, time.Second) {
m.AckSync()
}
ci, err := sub.ConsumerInfo()
require_NoError(t, err)
require_True(t, ci.AckFloor.Stream == 10)
// Stop current
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
// We will hand move the ackfloor to simulate dangling message condition.
cstore := filepath.Join(sd, "$G", "streams", "TEST", "obs", "dlc", "o.dat")
buf, err := os.ReadFile(cstore)
require_NoError(t, err)
state, err := decodeConsumerState(buf)
require_NoError(t, err)
// Update from 10 for delivered and ack to 90.
state.Delivered.Stream, state.Delivered.Consumer = 90, 90
state.AckFloor.Stream, state.AckFloor.Consumer = 90, 90
err = os.WriteFile(cstore, encodeConsumerState(state), defaultFilePerms)
require_NoError(t, err)
// Restart.
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
if si.State.Msgs != 10 {
t.Fatalf("Expected auto-cleanup to have worked but got %d msgs vs 10", si.State.Msgs)
}
}
// Issue https://github.com/nats-io/nats-server/issues/3645
func TestJetStreamMsgIDHeaderCollision(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"ORDERS.*"},
})
require_NoError(t, err)
m := nats.NewMsg("ORDERS.test")
m.Header.Add(JSMsgId, "1")
m.Data = []byte("ok")
_, err = js.PublishMsg(m)
require_NoError(t, err)
m.Header = make(nats.Header)
m.Header.Add("Orig-Nats-Msg-Id", "1")
_, err = js.PublishMsg(m)
require_NoError(t, err)
m.Header = make(nats.Header)
m.Header.Add("Original-Nats-Msg-Id", "1")
_, err = js.PublishMsg(m)
require_NoError(t, err)
m.Header = make(nats.Header)
m.Header.Add("Original-Nats-Msg-Id", "1")
m.Header.Add("Really-Original-Nats-Msg-Id", "1")
_, err = js.PublishMsg(m)
require_NoError(t, err)
m.Header = make(nats.Header)
m.Header.Add("X", "Nats-Msg-Id:1")
_, err = js.PublishMsg(m)
require_NoError(t, err)
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_True(t, si.State.Msgs == 5)
}
// https://github.com/nats-io/nats-server/issues/3657
func TestJetStreamServerCrashOnPullConsumerDeleteWithInactiveThresholdAfterAck(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
sendStreamMsg(t, nc, "foo", "msg")
sub, err := js.PullSubscribe("foo", "dlc", nats.InactiveThreshold(10*time.Second))
require_NoError(t, err)
msgs := fetchMsgs(t, sub, 1, time.Second)
require_True(t, len(msgs) == 1)
msgs[0].Ack()
err = js.DeleteConsumer("TEST", "dlc")
require_NoError(t, err)
// If server crashes this will fail.
_, err = js.StreamInfo("TEST")
require_NoError(t, err)
}
// createConsumer is a temporary method until nats.go client supports multiple subjects.
// it is used where lowe level call on mset is not enough, as we want to test error validation.
func createConsumer(t *testing.T, nc *nats.Conn, stream string, config ConsumerConfig) JSApiConsumerCreateResponse {
req, err := json.Marshal(&CreateConsumerRequest{Stream: stream, Config: config})
require_NoError(t, err)
resp, err := nc.Request(fmt.Sprintf("$JS.API.CONSUMER.DURABLE.CREATE.%s.%s", stream, config.Durable), req, time.Second*10)
require_NoError(t, err)
var apiResp JSApiConsumerCreateResponse
require_NoError(t, json.Unmarshal(resp.Data, &apiResp))
return apiResp
}
func TestJetStreamBothFiltersSet(t *testing.T) {
s := RunBasicJetStreamServer(t)
if config := s.JetStreamConfig(); config != nil {
defer removeDir(t, config.StoreDir)
}
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
_, err := acc.addStream(&StreamConfig{
Subjects: []string{"events.>"},
Name: "deliver",
})
require_NoError(t, err)
resp := createConsumer(t, nc, "deliver", ConsumerConfig{
FilterSubjects: []string{"events.one", "events.two"},
FilterSubject: "events.three",
Durable: "name",
})
require_True(t, resp.Error.ErrCode == 10136)
}
func TestJetStreamMultipleSubjectsPushBasic(t *testing.T) {
s := RunBasicJetStreamServer(t)
if config := s.JetStreamConfig(); config != nil {
defer removeDir(t, config.StoreDir)
}
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
mset, err := s.GlobalAccount().addStream(&StreamConfig{
Subjects: []string{"events", "data", "other"},
Name: "deliver",
})
require_NoError(t, err)
_, err = mset.addConsumer(&ConsumerConfig{
FilterSubjects: []string{"events", "data"},
Durable: "name",
DeliverSubject: "push",
})
require_NoError(t, err)
sub, err := nc.SubscribeSync("push")
require_NoError(t, err)
sendStreamMsg(t, nc, "other", "10")
sendStreamMsg(t, nc, "events", "0")
sendStreamMsg(t, nc, "data", "1")
sendStreamMsg(t, nc, "events", "2")
sendStreamMsg(t, nc, "events", "3")
sendStreamMsg(t, nc, "other", "10")
sendStreamMsg(t, nc, "data", "4")
sendStreamMsg(t, nc, "data", "5")
for i := 0; i < 6; i++ {
msg, err := sub.NextMsg(time.Second * 1)
require_NoError(t, err)
if fmt.Sprintf("%v", i) != string(msg.Data) {
t.Fatalf("bad sequence. Expected %v, got %v", i, string(msg.Data))
}
}
info, err := js.ConsumerInfo("deliver", "name")
require_NoError(t, err)
require_True(t, info.AckFloor.Consumer == 6)
require_True(t, info.AckFloor.Stream == 8)
}
func TestJetStreamMultipleSubjectsBasic(t *testing.T) {
s := RunBasicJetStreamServer(t)
if config := s.JetStreamConfig(); config != nil {
defer removeDir(t, config.StoreDir)
}
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc := s.GlobalAccount()
mset, err := acc.addStream(&StreamConfig{
Subjects: []string{"events", "data", "other"},
Name: "deliver",
})
require_NoError(t, err)
mset.addConsumer(&ConsumerConfig{
FilterSubjects: []string{"events", "data"},
Durable: "name",
})
require_NoError(t, err)
sendStreamMsg(t, nc, "other", "10")
sendStreamMsg(t, nc, "events", "0")
sendStreamMsg(t, nc, "data", "1")
sendStreamMsg(t, nc, "events", "2")
sendStreamMsg(t, nc, "events", "3")
sendStreamMsg(t, nc, "other", "10")
sendStreamMsg(t, nc, "data", "4")
sendStreamMsg(t, nc, "data", "5")
consumer, err := js.PullSubscribe("", "name", nats.Bind("deliver", "name"))
require_NoError(t, err)
msg, err := consumer.Fetch(6)
require_NoError(t, err)
for i, msg := range msg {
if fmt.Sprintf("%v", i) != string(msg.Data) {
t.Fatalf("bad sequence. Expected %v, got %v", i, string(msg.Data))
}
}
_, err = js.ConsumerInfo("deliver", "name")
require_NoError(t, err)
}
func TestJetStreamKVDelete(t *testing.T) {
s := RunBasicJetStreamServer(t)
if config := s.JetStreamConfig(); config != nil {
defer removeDir(t, config.StoreDir)
}
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
kv, err := js.CreateKeyValue(&nats.KeyValueConfig{
Bucket: "deletion",
History: 10,
})
require_NoError(t, err)
kv.Put("a", nil)
kv.Put("a.a", nil)
kv.Put("a.b", nil)
kv.Put("a.b.c", nil)
keys, err := kv.Keys()
require_NoError(t, err)
require_True(t, len(keys) == 4)
info, err := js.AddConsumer("KV_deletion", &nats.ConsumerConfig{
Name: "keys",
FilterSubject: "$KV.deletion.a.*",
DeliverPolicy: nats.DeliverLastPerSubjectPolicy,
DeliverSubject: "keys",
MaxDeliver: 1,
AckPolicy: nats.AckNonePolicy,
MemoryStorage: true,
FlowControl: true,
Heartbeat: time.Second * 5,
})
require_NoError(t, err)
require_True(t, info.NumPending == 2)
sub, err := js.SubscribeSync("$KV.deletion.a.*", nats.Bind("KV_deletion", "keys"))
require_NoError(t, err)
_, err = sub.NextMsg(time.Second * 1)
require_NoError(t, err)
_, err = sub.NextMsg(time.Second * 1)
require_NoError(t, err)
msg, err := sub.NextMsg(time.Second * 1)
require_True(t, msg == nil)
require_Error(t, err)
require_NoError(t, kv.Delete("a.a"))
require_NoError(t, kv.Delete("a.b"))
watcher, err := kv.WatchAll()
require_NoError(t, err)
updates := watcher.Updates()
keys = []string{}
for v := range updates {
if v == nil {
break
}
if v.Operation() == nats.KeyValueDelete {
keys = append(keys, v.Key())
}
}
require_True(t, len(keys) == 2)
}
func TestJetStreamDeliverLastPerSubjectWithKV(t *testing.T) {
s := RunBasicJetStreamServer(t)
if config := s.JetStreamConfig(); config != nil {
defer removeDir(t, config.StoreDir)
}
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
MaxMsgsPerSubject: 5,
Subjects: []string{"kv.>"},
})
require_NoError(t, err)
sendStreamMsg(t, nc, "kv.a", "bad")
sendStreamMsg(t, nc, "kv.a", "bad")
sendStreamMsg(t, nc, "kv.a", "bad")
sendStreamMsg(t, nc, "kv.a", "a")
sendStreamMsg(t, nc, "kv.a.b", "bad")
sendStreamMsg(t, nc, "kv.a.b", "bad")
sendStreamMsg(t, nc, "kv.a.b", "a.b")
sendStreamMsg(t, nc, "kv.a.b.c", "bad")
sendStreamMsg(t, nc, "kv.a.b.c", "bad")
sendStreamMsg(t, nc, "kv.a.b.c", "bad")
sendStreamMsg(t, nc, "kv.a.b.c", "a.b.c")
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Name: "CONSUMER",
FilterSubject: "kv.>",
DeliverPolicy: nats.DeliverLastPerSubjectPolicy,
DeliverSubject: "deliver",
MaxDeliver: 1,
AckPolicy: nats.AckNonePolicy,
MemoryStorage: true,
FlowControl: true,
Heartbeat: time.Second * 5,
})
require_NoError(t, err)
sub, err := js.SubscribeSync("kv.>", nats.Bind("TEST", "CONSUMER"))
require_NoError(t, err)
for i := 1; i <= 3; i++ {
_, err := sub.NextMsg(time.Second * 1)
require_NoError(t, err)
}
msg, err := sub.NextMsg(time.Second * 1)
if err == nil || msg != nil {
t.Fatalf("should not get any more messages")
}
}
func TestJetStreamStreamUpdateSubjectsOverlapOthers(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"TEST"},
})
require_NoError(t, err)
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"TEST", "foo.a"},
})
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST2",
Subjects: []string{"TEST2"},
})
require_NoError(t, err)
// we expect an error updating stream TEST2 with subject that overlaps that used by TEST
// foo.a fails too, but foo.* also double-check for sophisticated overlap match
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "TEST2",
Subjects: []string{"TEST2", "foo.*"},
})
require_Error(t, err)
require_Contains(t, err.Error(), "overlap")
}
func TestJetStreamMetaDataFailOnKernelFault(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
for i := 0; i < 10; i++ {
sendStreamMsg(t, nc, "foo", "OK")
}
sd := s.JetStreamConfig().StoreDir
sdir := filepath.Join(sd, "$G", "streams", "TEST")
s.Shutdown()
// Emulate if the kernel did not flush out to disk the meta information.
// so we will zero out both meta.inf and meta.sum.
err = os.WriteFile(filepath.Join(sdir, JetStreamMetaFile), nil, defaultFilePerms)
require_NoError(t, err)
err = os.WriteFile(filepath.Join(sdir, JetStreamMetaFileSum), nil, defaultFilePerms)
require_NoError(t, err)
// Restart.
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
// The stream will have not been recovered. So err is normal.
_, err = js.StreamInfo("TEST")
require_Error(t, err)
// Make sure we are signaled here from healthz
hs := s.healthz(nil)
const expected = "JetStream stream '$G > TEST' could not be recovered"
if hs.Status != "unavailable" || hs.Error == _EMPTY_ {
t.Fatalf("Expected healthz to return an error")
} else if hs.Error != expected {
t.Fatalf("Expected healthz error %q got %q", expected, hs.Error)
}
// If we add it back, this should recover the msgs.
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
// Make sure we recovered.
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_True(t, si.State.Msgs == 10)
// Now if we restart the server, meta should be correct,
// and the stream should be restored.
s.Shutdown()
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
// Make sure we recovered the stream correctly after re-adding.
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_True(t, si.State.Msgs == 10)
}
// https://github.com/nats-io/nats-server/issues/3734
func TestJetStreamMsgBlkFailOnKernelFault(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
MaxBytes: 10 * 1024 * 1024, // 10MB
})
require_NoError(t, err)
msgSize := 1024 * 1024 // 1MB
msg := make([]byte, msgSize)
crand.Read(msg)
for i := 0; i < 20; i++ {
_, err := js.Publish("foo", msg)
require_NoError(t, err)
}
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_True(t, si.State.Bytes < uint64(si.Config.MaxBytes))
// Now emulate a kernel fault that fails to write the last blk properly.
mset, err := s.GlobalAccount().lookupStream("TEST")
require_NoError(t, err)
mset.mu.RLock()
fs := mset.store.(*fileStore)
fs.mu.RLock()
require_True(t, len(fs.blks) > 2)
// Here we do not grab the last one, which we handle correctly. We grab an interior one near the end.
lmbf := fs.blks[len(fs.blks)-2].mfn
fs.mu.RUnlock()
mset.mu.RUnlock()
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
// Remove block.
require_NoError(t, os.Remove(lmbf))
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
_, err = js.GetMsg("TEST", 17)
require_Error(t, err, nats.ErrMsgNotFound)
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_True(t, si.State.NumDeleted == 3)
// Test detailed version as well.
si, err = js.StreamInfo("TEST", &nats.StreamInfoRequest{DeletedDetails: true})
require_NoError(t, err)
require_True(t, si.State.NumDeleted == 3)
if !reflect.DeepEqual(si.State.Deleted, []uint64{16, 17, 18}) {
t.Fatalf("Expected deleted of %+v, got %+v", []uint64{16, 17, 18}, si.State.Deleted)
}
for i := 0; i < 20; i++ {
_, err := js.Publish("foo", msg)
require_NoError(t, err)
}
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
if si.State.Bytes > uint64(si.Config.MaxBytes) {
t.Fatalf("MaxBytes not enforced with empty interior msg blk, max %v, bytes %v",
friendlyBytes(si.Config.MaxBytes), friendlyBytes(int64(si.State.Bytes)))
}
}
func TestJetStreamPurgeExAndAccounting(t *testing.T) {
cases := []struct {
name string
cfg *nats.StreamConfig
}{
{name: "MemoryStore",
cfg: &nats.StreamConfig{
Name: "TEST",
Storage: nats.MemoryStorage,
Subjects: []string{"*"},
}},
{name: "FileStore",
cfg: &nats.StreamConfig{
Name: "TEST",
Storage: nats.FileStorage,
Subjects: []string{"*"},
}},
}
for _, c := range cases {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(c.cfg)
require_NoError(t, err)
msg := []byte("accounting")
for i := 0; i < 100; i++ {
_, err = js.Publish("foo", msg)
require_NoError(t, err)
_, err = js.Publish("bar", msg)
require_NoError(t, err)
}
info, err := js.AccountInfo()
require_NoError(t, err)
err = js.PurgeStream("TEST", &nats.StreamPurgeRequest{Subject: "foo"})
require_NoError(t, err)
ninfo, err := js.AccountInfo()
require_NoError(t, err)
// Make sure we did the proper accounting.
if c.cfg.Storage == nats.MemoryStorage {
if ninfo.Memory != info.Memory/2 {
t.Fatalf("Accounting information incorrect for Memory: %d vs %d",
ninfo.Memory, info.Memory/2)
}
} else {
if ninfo.Store != info.Store/2 {
t.Fatalf("Accounting information incorrect for FileStore: %d vs %d",
ninfo.Store, info.Store/2)
}
}
}
}
func TestJetStreamRollup(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
const STREAM = "S"
const SUBJ = "S.*"
js.AddStream(&nats.StreamConfig{
Name: STREAM,
Subjects: []string{SUBJ},
AllowRollup: true,
})
for i := 1; i <= 10; i++ {
sendStreamMsg(t, nc, "S.A", fmt.Sprintf("%v", i))
sendStreamMsg(t, nc, "S.B", fmt.Sprintf("%v", i))
}
sinfo, err := js.StreamInfo(STREAM)
require_NoError(t, err)
require_True(t, sinfo.State.Msgs == 20)
cinfo, err := js.AddConsumer(STREAM, &nats.ConsumerConfig{
Durable: "DUR-A",
FilterSubject: "S.A",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
require_True(t, cinfo.NumPending == 10)
m := nats.NewMsg("S.A")
m.Header.Set(JSMsgRollup, JSMsgRollupSubject)
_, err = js.PublishMsg(m)
require_NoError(t, err)
cinfo, err = js.ConsumerInfo("S", "DUR-A")
require_NoError(t, err)
require_True(t, cinfo.NumPending == 1)
sinfo, err = js.StreamInfo(STREAM)
require_NoError(t, err)
require_True(t, sinfo.State.Msgs == 11)
cinfo, err = js.AddConsumer(STREAM, &nats.ConsumerConfig{
Durable: "DUR-B",
FilterSubject: "S.B",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
require_True(t, cinfo.NumPending == 10)
}
func TestJetStreamPartialPurgeWithAckPending(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
nmsgs := 100
for i := 0; i < nmsgs; i++ {
sendStreamMsg(t, nc, "foo", "OK")
}
sub, err := js.PullSubscribe("foo", "dlc", nats.AckWait(time.Second))
require_NoError(t, err)
// Queue up all for ack pending.
_, err = sub.Fetch(nmsgs)
require_NoError(t, err)
keep := nmsgs / 2
require_NoError(t, js.PurgeStream("TEST", &nats.StreamPurgeRequest{Keep: uint64(keep)}))
// Should be able to be redelivered now.
time.Sleep(2 * time.Second)
ci, err := js.ConsumerInfo("TEST", "dlc")
require_NoError(t, err)
// Make sure we calculated correctly.
require_True(t, ci.AckFloor.Consumer == uint64(keep))
require_True(t, ci.AckFloor.Stream == uint64(keep))
require_True(t, ci.NumAckPending == keep)
require_True(t, ci.NumPending == 0)
for i := 0; i < nmsgs; i++ {
sendStreamMsg(t, nc, "foo", "OK")
}
ci, err = js.ConsumerInfo("TEST", "dlc")
require_NoError(t, err)
// Make sure we calculated correctly.
// Top 3 will be same.
require_True(t, ci.AckFloor.Consumer == uint64(keep))
require_True(t, ci.AckFloor.Stream == uint64(keep))
require_True(t, ci.NumAckPending == keep)
require_True(t, ci.NumPending == uint64(nmsgs))
require_True(t, ci.NumRedelivered == 0)
msgs, err := sub.Fetch(keep)
require_NoError(t, err)
require_True(t, len(msgs) == keep)
ci, err = js.ConsumerInfo("TEST", "dlc")
require_NoError(t, err)
// Make sure we calculated correctly.
require_True(t, ci.Delivered.Consumer == uint64(nmsgs+keep))
require_True(t, ci.Delivered.Stream == uint64(nmsgs))
require_True(t, ci.AckFloor.Consumer == uint64(keep))
require_True(t, ci.AckFloor.Stream == uint64(keep))
require_True(t, ci.NumAckPending == keep)
require_True(t, ci.NumPending == uint64(nmsgs))
require_True(t, ci.NumRedelivered == keep)
// Ack all.
for _, m := range msgs {
m.Ack()
}
nc.Flush()
ci, err = js.ConsumerInfo("TEST", "dlc")
require_NoError(t, err)
// Same for Delivered
require_True(t, ci.Delivered.Consumer == uint64(nmsgs+keep))
require_True(t, ci.Delivered.Stream == uint64(nmsgs))
require_True(t, ci.AckFloor.Consumer == uint64(nmsgs+keep))
require_True(t, ci.AckFloor.Stream == uint64(nmsgs))
require_True(t, ci.NumAckPending == 0)
require_True(t, ci.NumPending == uint64(nmsgs))
require_True(t, ci.NumRedelivered == 0)
msgs, err = sub.Fetch(nmsgs)
require_NoError(t, err)
require_True(t, len(msgs) == nmsgs)
// Ack all again
for _, m := range msgs {
m.Ack()
}
nc.Flush()
ci, err = js.ConsumerInfo("TEST", "dlc")
require_NoError(t, err)
// Make sure we calculated correctly.
require_True(t, ci.Delivered.Consumer == uint64(nmsgs*2+keep))
require_True(t, ci.Delivered.Stream == uint64(nmsgs*2))
require_True(t, ci.AckFloor.Consumer == uint64(nmsgs*2+keep))
require_True(t, ci.AckFloor.Stream == uint64(nmsgs*2))
require_True(t, ci.NumAckPending == 0)
require_True(t, ci.NumPending == 0)
require_True(t, ci.NumRedelivered == 0)
}
func TestJetStreamPurgeWithRedeliveredPending(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
nmsgs := 100
for i := 0; i < nmsgs; i++ {
sendStreamMsg(t, nc, "foo", "OK")
}
sub, err := js.PullSubscribe("foo", "dlc", nats.AckWait(time.Second))
require_NoError(t, err)
// Queue up all for ack pending.
msgs, err := sub.Fetch(nmsgs)
require_NoError(t, err)
require_True(t, len(msgs) == nmsgs)
// Should be able to be redelivered now.
time.Sleep(2 * time.Second)
// Queue up all for ack pending again.
msgs, err = sub.Fetch(nmsgs)
require_NoError(t, err)
require_True(t, len(msgs) == nmsgs)
require_NoError(t, js.PurgeStream("TEST"))
ci, err := js.ConsumerInfo("TEST", "dlc")
require_NoError(t, err)
require_True(t, ci.Delivered.Consumer == uint64(2*nmsgs))
require_True(t, ci.Delivered.Stream == uint64(nmsgs))
require_True(t, ci.AckFloor.Consumer == uint64(2*nmsgs))
require_True(t, ci.AckFloor.Stream == uint64(nmsgs))
require_True(t, ci.NumAckPending == 0)
require_True(t, ci.NumPending == 0)
require_True(t, ci.NumRedelivered == 0)
}
func TestJetStreamStreamUpdateWithExternalSource(t *testing.T) {
ho := DefaultTestOptions
ho.Port = -1
ho.LeafNode.Host = "127.0.0.1"
ho.LeafNode.Port = -1
ho.JetStream = true
ho.JetStreamDomain = "hub"
ho.StoreDir = t.TempDir()
hs := RunServer(&ho)
defer hs.Shutdown()
lu, err := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", ho.LeafNode.Port))
require_NoError(t, err)
lo1 := DefaultTestOptions
lo1.Port = -1
lo1.ServerName = "a-leaf"
lo1.JetStream = true
lo1.StoreDir = t.TempDir()
lo1.JetStreamDomain = "a-leaf"
lo1.LeafNode.Remotes = []*RemoteLeafOpts{{URLs: []*url.URL{lu}}}
l1 := RunServer(&lo1)
defer l1.Shutdown()
checkLeafNodeConnected(t, l1)
// Test sources with `External` provided
ncl, jsl := jsClientConnect(t, l1)
defer ncl.Close()
// Hub stream.
_, err = jsl.AddStream(&nats.StreamConfig{Name: "stream", Subjects: []string{"leaf"}})
require_NoError(t, err)
nch, jsh := jsClientConnect(t, hs)
defer nch.Close()
// Leaf stream.
// Both streams uses the same name, as we're testing if overlap does not check against itself
// if `External` stream has the same name.
_, err = jsh.AddStream(&nats.StreamConfig{
Name: "stream",
Subjects: []string{"hub"},
})
require_NoError(t, err)
// Add `Sources`.
// This should not validate subjects overlap against itself.
_, err = jsh.UpdateStream(&nats.StreamConfig{
Name: "stream",
Subjects: []string{"hub"},
Sources: []*nats.StreamSource{
{
Name: "stream",
FilterSubject: "leaf",
External: &nats.ExternalStream{
APIPrefix: "$JS.a-leaf.API",
},
},
},
})
require_NoError(t, err)
// Specifying not existing FilterSubject should also be fine, as we do not validate `External` stream.
_, err = jsh.UpdateStream(&nats.StreamConfig{
Name: "stream",
Subjects: []string{"hub"},
Sources: []*nats.StreamSource{
{
Name: "stream",
FilterSubject: "foo",
External: &nats.ExternalStream{
APIPrefix: "$JS.a-leaf.API",
},
},
},
})
require_NoError(t, err)
}
func TestJetStreamKVHistoryRegression(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
for _, storage := range []nats.StorageType{nats.FileStorage, nats.MemoryStorage} {
t.Run(storage.String(), func(t *testing.T) {
js.DeleteKeyValue("TEST")
kv, err := js.CreateKeyValue(&nats.KeyValueConfig{
Bucket: "TEST",
History: 4,
Storage: storage,
})
require_NoError(t, err)
r1, err := kv.Create("foo", []byte("a"))
require_NoError(t, err)
_, err = kv.Update("foo", []byte("ab"), r1)
require_NoError(t, err)
err = kv.Delete("foo")
require_NoError(t, err)
_, err = kv.Create("foo", []byte("abc"))
require_NoError(t, err)
err = kv.Delete("foo")
require_NoError(t, err)
history, err := kv.History("foo")
require_NoError(t, err)
require_True(t, len(history) == 4)
_, err = kv.Update("foo", []byte("abcd"), history[len(history)-1].Revision())
require_NoError(t, err)
err = kv.Purge("foo")
require_NoError(t, err)
_, err = kv.Create("foo", []byte("abcde"))
require_NoError(t, err)
err = kv.Purge("foo")
require_NoError(t, err)
history, err = kv.History("foo")
require_NoError(t, err)
require_True(t, len(history) == 1)
})
}
}
func TestJetStreamSnapshotRestoreStallAndHealthz(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "ORDERS",
Subjects: []string{"orders.*"},
})
require_NoError(t, err)
for i := 0; i < 1000; i++ {
sendStreamMsg(t, nc, "orders.created", "new order")
}
hs := s.healthz(nil)
if hs.Status != "ok" || hs.Error != _EMPTY_ {
t.Fatalf("Expected health to be ok, got %+v", hs)
}
// Simulate the staging directory for restores. This is normally cleaned up
// but since its at the root of the storage directory make sure healthz is not affected.
snapDir := filepath.Join(s.getJetStream().config.StoreDir, snapStagingDir)
require_NoError(t, os.MkdirAll(snapDir, defaultDirPerms))
// Make sure healthz ok.
hs = s.healthz(nil)
if hs.Status != "ok" || hs.Error != _EMPTY_ {
t.Fatalf("Expected health to be ok, got %+v", hs)
}
}
// https://github.com/nats-io/nats-server/pull/4163
func TestJetStreamMaxBytesIgnored(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"*"},
MaxBytes: 10 * 1024 * 1024,
})
require_NoError(t, err)
msg := bytes.Repeat([]byte("A"), 1024*1024)
for i := 0; i < 10; i++ {
_, err := js.Publish("x", msg)
require_NoError(t, err)
}
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_True(t, si.State.Msgs == 9)
// Stop current
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
// We will truncate blk file.
mdir := filepath.Join(sd, "$G", "streams", "TEST", "msgs")
// Truncate blk
err = os.WriteFile(filepath.Join(mdir, "1.blk"), nil, defaultFilePerms)
require_NoError(t, err)
// Restart.
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
for i := 0; i < 10; i++ {
_, err := js.Publish("x", msg)
require_NoError(t, err)
}
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_True(t, si.State.Bytes <= 10*1024*1024)
}
func TestJetStreamLastSequenceBySubjectConcurrent(t *testing.T) {
for _, st := range []StorageType{FileStorage, MemoryStorage} {
t.Run(st.String(), func(t *testing.T) {
c := createJetStreamClusterExplicit(t, "JSC", 3)
defer c.shutdown()
nc0, js0 := jsClientConnect(t, c.randomServer())
defer nc0.Close()
nc1, js1 := jsClientConnect(t, c.randomServer())
defer nc1.Close()
cfg := StreamConfig{
Name: "KV",
Subjects: []string{"kv.>"},
Storage: st,
Replicas: 3,
}
req, err := json.Marshal(cfg)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Do manually for now.
m, err := nc0.Request(fmt.Sprintf(JSApiStreamCreateT, cfg.Name), req, time.Second)
require_NoError(t, err)
si, err := js0.StreamInfo("KV")
if err != nil {
t.Fatalf("Unexpected error: %v, respmsg: %q", err, string(m.Data))
}
if si == nil || si.Config.Name != "KV" {
t.Fatalf("StreamInfo is not correct %+v", si)
}
pub := func(js nats.JetStreamContext, subj, data, seq string) {
t.Helper()
m := nats.NewMsg(subj)
m.Data = []byte(data)
m.Header.Set(JSExpectedLastSubjSeq, seq)
js.PublishMsg(m)
}
ready := make(chan struct{})
wg := &sync.WaitGroup{}
wg.Add(2)
go func() {
<-ready
pub(js0, "kv.foo", "0-0", "0")
pub(js0, "kv.foo", "0-1", "1")
pub(js0, "kv.foo", "0-2", "2")
wg.Done()
}()
go func() {
<-ready
pub(js1, "kv.foo", "1-0", "0")
pub(js1, "kv.foo", "1-1", "1")
pub(js1, "kv.foo", "1-2", "2")
wg.Done()
}()
time.Sleep(50 * time.Millisecond)
close(ready)
wg.Wait()
// Read the messages.
sub, err := js0.PullSubscribe(_EMPTY_, _EMPTY_, nats.BindStream("KV"))
require_NoError(t, err)
msgs, err := sub.Fetch(10)
require_NoError(t, err)
if len(msgs) != 3 {
t.Errorf("Expected 3 messages, got %d", len(msgs))
}
for i, m := range msgs {
if m.Header.Get(JSExpectedLastSubjSeq) != fmt.Sprint(i) {
t.Errorf("Expected %d for last sequence, got %q", i, m.Header.Get(JSExpectedLastSubjSeq))
}
}
})
}
}
func TestJetStreamServerReencryption(t *testing.T) {
storeDir := t.TempDir()
var i int
for _, algo := range []struct {
from string
to string
}{
{"aes", "aes"},
{"aes", "chacha"},
{"chacha", "chacha"},
{"chacha", "aes"},
} {
for _, compression := range []StoreCompression{NoCompression, S2Compression} {
t.Run(fmt.Sprintf("%s_to_%s/%s", algo.from, algo.to, compression), func(t *testing.T) {
i++
streamName := fmt.Sprintf("TEST_%d", i)
subjectName := fmt.Sprintf("foo_%d", i)
expected := 30
checkStream := func(js nats.JetStreamContext) {
si, err := js.StreamInfo(streamName)
if err != nil {
t.Fatal(err)
}
if si.State.Msgs != uint64(expected) {
t.Fatalf("Should be %d messages but got %d messages", expected, si.State.Msgs)
}
sub, err := js.PullSubscribe(subjectName, "")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
c := 0
for _, m := range fetchMsgs(t, sub, expected, 5*time.Second) {
m.AckSync()
c++
}
if c != expected {
t.Fatalf("Should have read back %d messages but got %d messages", expected, c)
}
}
// First off, we start up using the original encryption key and algorithm.
// We'll create a stream and populate it with some messages.
t.Run("setup", func(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
server_name: S22
listen: 127.0.0.1:-1
jetstream: {
key: %q,
cipher: %s,
store_dir: %q
}
`, "firstencryptionkey", algo.from, storeDir)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &StreamConfig{
Name: streamName,
Subjects: []string{subjectName},
Storage: FileStorage,
Compression: compression,
}
if _, err := jsStreamCreate(t, nc, cfg); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
payload := strings.Repeat("A", 512*1024)
for i := 0; i < expected; i++ {
if _, err := js.Publish(subjectName, []byte(payload)); err != nil {
t.Fatalf("Unexpected publish error: %v", err)
}
}
checkStream(js)
})
// Next up, we will restart the server, this time with both the new key
// and algorithm and also the old key. At startup, the server will detect
// the change in encryption key and/or algorithm and re-encrypt the stream.
t.Run("reencrypt", func(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
server_name: S22
listen: 127.0.0.1:-1
jetstream: {
key: %q,
cipher: %s,
prev_key: %q,
store_dir: %q
}
`, "secondencryptionkey", algo.to, "firstencryptionkey", storeDir)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
checkStream(js)
})
// Finally, we'll restart the server using only the new key and algorithm.
// At this point everything should have been re-encrypted, so we should still
// be able to access the stream.
t.Run("restart", func(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(`
server_name: S22
listen: 127.0.0.1:-1
jetstream: {
key: %q,
cipher: %s,
store_dir: %q
}
`, "secondencryptionkey", algo.to, storeDir)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
checkStream(js)
})
})
}
}
}
func TestJetStreamLimitsToInterestPolicy(t *testing.T) {
c := createJetStreamClusterExplicit(t, "JSC", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.leader())
defer nc.Close()
// This is the index of the consumer that we'll create as R1
// instead of R3, just to prove that it blocks the stream
// update from happening properly.
singleReplica := 3
streamCfg := nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Retention: nats.LimitsPolicy,
Storage: nats.MemoryStorage,
Replicas: 3,
}
stream, err := js.AddStream(&streamCfg)
require_NoError(t, err)
for i := 0; i < 10; i++ {
replicas := streamCfg.Replicas
if i == singleReplica {
// Make one of the consumers R1 so that we can check
// that the switch to interest-based retention is also
// turning it into an R3 consumer.
replicas = 1
}
cname := fmt.Sprintf("test_%d", i)
_, err := js.AddConsumer("TEST", &nats.ConsumerConfig{
Name: cname,
Durable: cname,
AckPolicy: nats.AckAllPolicy,
Replicas: replicas,
})
require_NoError(t, err)
}
for i := 0; i < 20; i++ {
_, err := js.Publish("foo", []byte{1, 2, 3, 4, 5})
require_NoError(t, err)
}
// Pull 10 or more messages from the stream. We will never pull
// less than 10, which guarantees that the lowest ack floor of
// all consumers should be 10.
for i := 0; i < 10; i++ {
cname := fmt.Sprintf("test_%d", i)
count := 10 + i // At least 10 messages
sub, err := js.PullSubscribe("foo", cname)
require_NoError(t, err)
msgs, err := sub.Fetch(count)
require_NoError(t, err)
require_Equal(t, len(msgs), count)
require_NoError(t, msgs[len(msgs)-1].AckSync())
// At this point the ack floor should match the count of
// messages we received.
info, err := js.ConsumerInfo("TEST", cname)
require_NoError(t, err)
require_Equal(t, info.AckFloor.Consumer, uint64(count))
}
// Try updating to interest-based. This should fail because
// we have a consumer that is R1 on an R3 stream.
streamCfg = stream.Config
streamCfg.Retention = nats.InterestPolicy
_, err = js.UpdateStream(&streamCfg)
require_Error(t, err)
// Now we'll make the R1 consumer an R3.
cname := fmt.Sprintf("test_%d", singleReplica)
cinfo, err := js.ConsumerInfo("TEST", cname)
require_NoError(t, err)
cinfo.Config.Replicas = streamCfg.Replicas
_, _ = js.UpdateConsumer("TEST", &cinfo.Config)
// TODO(nat): The jsConsumerCreateRequest update doesn't always
// respond when there are no errors updating a consumer, so this
// nearly always returns a timeout, despite actually doing what
// it should. We'll make sure the replicas were updated by doing
// another consumer info just to be sure.
// require_NoError(t, err)
c.waitOnAllCurrent()
c.waitOnConsumerLeader(globalAccountName, "TEST", cname)
cinfo, err = js.ConsumerInfo("TEST", cname)
require_NoError(t, err)
require_Equal(t, cinfo.Config.Replicas, streamCfg.Replicas)
require_Equal(t, len(cinfo.Cluster.Replicas), streamCfg.Replicas-1)
// This time it should succeed.
_, err = js.UpdateStream(&streamCfg)
require_NoError(t, err)
// We need to wait for all nodes to have applied the new stream
// configuration.
c.waitOnAllCurrent()
// Now we should only have 10 messages left in the stream, as
// each consumer has acked at least the first 10 messages.
info, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, info.State.FirstSeq, 11)
require_Equal(t, info.State.Msgs, 10)
}
func TestJetStreamLimitsToInterestPolicyWhileAcking(t *testing.T) {
for _, st := range []nats.StorageType{nats.FileStorage, nats.MemoryStorage} {
t.Run(st.String(), func(t *testing.T) {
c := createJetStreamClusterExplicit(t, "JSC", 3)
defer c.shutdown()
nc, js := jsClientConnect(t, c.leader())
defer nc.Close()
streamCfg := nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Retention: nats.LimitsPolicy,
Storage: st,
Replicas: 3,
}
stream, err := js.AddStream(&streamCfg)
require_NoError(t, err)
wg := sync.WaitGroup{}
ctx, cancel := context.WithCancel(context.Background())
payload := []byte(strings.Repeat("A", 128))
wg.Add(1)
go func() {
defer wg.Done()
for range time.NewTicker(10 * time.Millisecond).C {
select {
case <-ctx.Done():
return
default:
}
js.Publish("foo", payload)
}
}()
for i := 0; i < 5; i++ {
cname := fmt.Sprintf("test_%d", i)
sub, err := js.PullSubscribe("foo", cname)
require_NoError(t, err)
wg.Add(1)
go func() {
defer wg.Done()
for range time.NewTicker(10 * time.Millisecond).C {
select {
case <-ctx.Done():
return
default:
}
msgs, err := sub.Fetch(1)
if err != nil && errors.Is(err, nats.ErrTimeout) {
t.Logf("ERROR: %v", err)
}
for _, msg := range msgs {
msg.Ack()
}
}
}()
}
// Leave running for a few secs then do the change on a different connection.
time.Sleep(5 * time.Second)
nc2, js2 := jsClientConnect(t, c.leader())
defer nc2.Close()
// Try updating to interest-based and changing replicas too.
streamCfg = stream.Config
streamCfg.Retention = nats.InterestPolicy
_, err = js2.UpdateStream(&streamCfg)
require_NoError(t, err)
// We need to wait for all nodes to have applied the new stream
// configuration.
c.waitOnAllCurrent()
var retention nats.RetentionPolicy
checkFor(t, 15*time.Second, 500*time.Millisecond, func() error {
info, err := js2.StreamInfo("TEST", nats.MaxWait(500*time.Millisecond))
if err != nil {
return err
}
retention = info.Config.Retention
return nil
})
require_Equal(t, retention, nats.InterestPolicy)
// Cancel and wait for goroutines underneath.
cancel()
wg.Wait()
})
}
}
func TestJetStreamUsageSyncDeadlock(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"*"},
})
require_NoError(t, err)
sendStreamMsg(t, nc, "foo", "hello")
// Now purposely mess up the usage that will force a sync.
// Without the fix this will deadlock.
jsa := s.getJetStream().lookupAccount(s.GlobalAccount())
jsa.usageMu.Lock()
st, ok := jsa.usage[_EMPTY_]
require_True(t, ok)
st.local.store = -1000
jsa.usageMu.Unlock()
sendStreamMsg(t, nc, "foo", "hello")
}
// https://github.com/nats-io/nats.go/issues/1382
// https://github.com/nats-io/nats-server/issues/4445
func TestJetStreamChangeMaxMessagesPerSubject(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"one.>"},
MaxMsgsPerSubject: 5,
})
require_NoError(t, err)
for i := 0; i < 10; i++ {
sendStreamMsg(t, nc, "one.data", "data")
}
expectMsgs := func(num int32) error {
t.Helper()
var msgs atomic.Int32
sub, err := js.Subscribe("one.>", func(msg *nats.Msg) {
msgs.Add(1)
msg.Ack()
})
require_NoError(t, err)
defer sub.Unsubscribe()
checkFor(t, 5*time.Second, 100*time.Millisecond, func() error {
if nm := msgs.Load(); nm != num {
return fmt.Errorf("expected to get %v messages, got %v instead", num, nm)
}
return nil
})
return nil
}
require_NoError(t, expectMsgs(5))
js.UpdateStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"one.>"},
MaxMsgsPerSubject: 3,
})
info, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_True(t, info.Config.MaxMsgsPerSubject == 3)
require_True(t, info.State.Msgs == 3)
require_NoError(t, expectMsgs(3))
for i := 0; i < 10; i++ {
sendStreamMsg(t, nc, "one.data", "data")
}
require_NoError(t, expectMsgs(3))
}
func TestJetStreamSyncInterval(t *testing.T) {
sd := t.TempDir()
tmpl := `
listen: 127.0.0.1:-1
jetstream: {
store_dir: %q
%s
}`
for _, test := range []struct {
name string
sync string
expected time.Duration
always bool
}{
{"Default", _EMPTY_, defaultSyncInterval, false},
{"10s", "sync_interval: 10s", time.Duration(10 * time.Second), false},
{"Always", "sync_interval: always", defaultSyncInterval, true},
} {
t.Run(test.name, func(t *testing.T) {
conf := createConfFile(t, []byte(fmt.Sprintf(tmpl, sd, test.sync)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
opts := s.getOpts()
require_True(t, opts.SyncInterval == test.expected)
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"test.>"},
})
require_NoError(t, err)
mset, err := s.GlobalAccount().lookupStream("TEST")
require_NoError(t, err)
fs := mset.store.(*fileStore)
fs.mu.RLock()
fsSync := fs.fcfg.SyncInterval
syncAlways := fs.fcfg.SyncAlways
fs.mu.RUnlock()
require_True(t, fsSync == test.expected)
require_True(t, syncAlways == test.always)
})
}
}
func TestJetStreamFilteredSubjectUsesNewConsumerCreateSubject(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
extEndpoint := make(chan *nats.Msg, 1)
normalEndpoint := make(chan *nats.Msg, 1)
_, err := nc.ChanSubscribe(JSApiConsumerCreateEx, extEndpoint)
require_NoError(t, err)
_, err = nc.ChanSubscribe(JSApiConsumerCreate, normalEndpoint)
require_NoError(t, err)
testStreamSource := func(name string, shouldBeExtended bool, ss StreamSource) {
t.Run(name, func(t *testing.T) {
req := StreamConfig{
Name: name,
Storage: MemoryStorage,
Subjects: []string{fmt.Sprintf("foo.%s", name)},
Sources: []*StreamSource{&ss},
}
reqJson, err := json.Marshal(req)
require_NoError(t, err)
_, err = nc.Request(fmt.Sprintf(JSApiStreamCreateT, name), reqJson, time.Second)
require_NoError(t, err)
select {
case <-time.After(time.Second * 5):
t.Fatalf("Timed out waiting for receive consumer create")
case <-extEndpoint:
if !shouldBeExtended {
t.Fatalf("Expected normal consumer create, got extended")
}
case <-normalEndpoint:
if shouldBeExtended {
t.Fatalf("Expected extended consumer create, got normal")
}
}
})
}
testStreamSource("OneFilterSubject", true, StreamSource{
Name: "source",
FilterSubject: "bar.>",
})
testStreamSource("OneTransform", true, StreamSource{
Name: "source",
SubjectTransforms: []SubjectTransformConfig{
{
Source: "bar.one.>",
Destination: "bar.two.>",
},
},
})
testStreamSource("TwoTransforms", false, StreamSource{
Name: "source",
SubjectTransforms: []SubjectTransformConfig{
{
Source: "bar.one.>",
Destination: "bar.two.>",
},
{
Source: "baz.one.>",
Destination: "baz.two.>",
},
},
})
}
// Make sure when we downgrade history to a smaller number that the account info
// is properly updated and all keys are still accessible.
// There was a bug calculating next first that was not taking into account the dbit slots.
func TestJetStreamKVReductionInHistory(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
startHistory := 4
kv, err := js.CreateKeyValue(&nats.KeyValueConfig{Bucket: "TEST", History: uint8(startHistory)})
require_NoError(t, err)
numKeys, msg := 1000, bytes.Repeat([]byte("ABC"), 330) // ~1000bytes
for {
key := fmt.Sprintf("%X", rand.Intn(numKeys)+1)
_, err = kv.Put(key, msg)
require_NoError(t, err)
status, err := kv.Status()
require_NoError(t, err)
if status.Values() >= uint64(startHistory*numKeys) {
break
}
}
info, err := js.AccountInfo()
require_NoError(t, err)
checkAllKeys := func() {
t.Helper()
// Make sure we can retrieve all of the keys.
keys, err := kv.Keys()
require_NoError(t, err)
require_Equal(t, len(keys), numKeys)
for _, key := range keys {
_, err := kv.Get(key)
require_NoError(t, err)
}
}
// Quick sanity check.
checkAllKeys()
si, err := js.StreamInfo("KV_TEST")
require_NoError(t, err)
// Adjust down to history of 1.
cfg := si.Config
cfg.MaxMsgsPerSubject = 1
_, err = js.UpdateStream(&cfg)
require_NoError(t, err)
// Make sure the accounting was updated.
ninfo, err := js.AccountInfo()
require_NoError(t, err)
require_True(t, info.Store > ninfo.Store)
// Make sure all keys still accessible.
checkAllKeys()
}
func TestJetStreamDirectGetBatch(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
})
require_NoError(t, err)
// Add in messages
for i := 0; i < 333; i++ {
js.PublishAsync("foo.foo", []byte("HELLO"))
js.PublishAsync("foo.bar", []byte("WORLD"))
js.PublishAsync("foo.baz", []byte("AGAIN"))
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
// DirectGet is required for batch. Make sure we error correctly if not enabled.
mreq := &JSApiMsgGetRequest{Seq: 1, Batch: 10}
req, _ := json.Marshal(mreq)
rr, err := nc.Request("$JS.API.STREAM.MSG.GET.TEST", req, time.Second)
require_NoError(t, err)
var resp JSApiMsgGetResponse
json.Unmarshal(rr.Data, &resp)
require_True(t, resp.Error != nil)
require_Equal(t, resp.Error.Code, NewJSBadRequestError().Code)
// Update stream to support direct.
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
AllowDirect: true,
})
require_NoError(t, err)
// Direct subjects.
sendRequest := func(mreq *JSApiMsgGetRequest) *nats.Subscription {
t.Helper()
req, _ := json.Marshal(mreq)
// We will get multiple responses so can't do normal request.
reply := nats.NewInbox()
sub, err := nc.SubscribeSync(reply)
require_NoError(t, err)
err = nc.PublishRequest("$JS.API.DIRECT.GET.TEST", reply, req)
require_NoError(t, err)
return sub
}
// Batch sizes greater than 1 will have a nil message as the end marker.
checkResponses := func(sub *nats.Subscription, numPendingStart int, expected ...string) {
t.Helper()
defer sub.Unsubscribe()
checkSubsPending(t, sub, len(expected))
np := numPendingStart
last := "0"
for i := 0; i < len(expected); i++ {
msg, err := sub.NextMsg(10 * time.Millisecond)
require_NoError(t, err)
// If expected is _EMPTY_ that signals we expect a EOB marker.
if subj := expected[i]; subj != _EMPTY_ {
// Make sure subject is correct.
require_Equal(t, expected[i], msg.Header.Get(JSSubject))
// Should have Data field non-zero
require_True(t, len(msg.Data) > 0)
// Check we have NumPending and it's correct.
if np > 0 {
np--
}
require_Equal(t, strconv.Itoa(np), msg.Header.Get(JSNumPending))
require_Equal(t, last, msg.Header.Get(JSLastSequence))
last = msg.Header.Get(JSSequence)
} else {
// Check for properly formatted EOB marker.
// Should have no body.
require_Equal(t, len(msg.Data), 0)
// We mark status as 204 - No Content
require_Equal(t, msg.Header.Get("Status"), "204")
// Check description is EOB
require_Equal(t, msg.Header.Get("Description"), "EOB")
// Check we have NumPending and it's correct.
require_Equal(t, strconv.Itoa(np), msg.Header.Get(JSNumPending))
require_Equal(t, last, msg.Header.Get(JSLastSequence))
}
}
}
// Run some simple tests.
sub := sendRequest(&JSApiMsgGetRequest{Seq: 1, Batch: 2})
checkResponses(sub, 999, "foo.foo", "foo.bar", _EMPTY_)
sub = sendRequest(&JSApiMsgGetRequest{Seq: 1, Batch: 3})
checkResponses(sub, 999, "foo.foo", "foo.bar", "foo.baz", _EMPTY_)
// Test NextFor works
sub = sendRequest(&JSApiMsgGetRequest{Seq: 1, Batch: 3, NextFor: "foo.*"})
checkResponses(sub, 999, "foo.foo", "foo.bar", "foo.baz", _EMPTY_)
sub = sendRequest(&JSApiMsgGetRequest{Seq: 1, Batch: 3, NextFor: "foo.baz"})
checkResponses(sub, 333, "foo.baz", "foo.baz", "foo.baz", _EMPTY_)
// Test stopping early by starting at 997 with only 3 messages.
sub = sendRequest(&JSApiMsgGetRequest{Seq: 997, Batch: 10, NextFor: "foo.*"})
checkResponses(sub, 3, "foo.foo", "foo.bar", "foo.baz", _EMPTY_)
err = js.PurgeStream("TEST")
require_NoError(t, err)
// Add in messages
js.PublishAsync("foo.foo", []byte("HELLO"))
js.PublishAsync("foo.bar", []byte("WORLD"))
js.PublishAsync("foo.baz", []byte("AGAIN"))
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
// Verify that a batch get request starting at a deleted sequence number skips over the deleted messages.
sub = sendRequest(&JSApiMsgGetRequest{Seq: 1, Batch: 10})
checkResponses(sub, 3, "foo.foo", "foo.bar", "foo.baz", _EMPTY_)
// verify that a non-batch direct get to a deleted message returns not found.
_, err = js.GetMsg("TEST", 1, nats.DirectGet())
require_Error(t, err, fmt.Errorf("nats: message not found"))
}
func TestJetStreamDirectGetBatchMaxBytes(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
AllowDirect: true,
Compression: nats.S2Compression,
})
require_NoError(t, err)
msg := bytes.Repeat([]byte("Z"), 512*1024)
// Add in messages
for i := 0; i < 333; i++ {
js.PublishAsync("foo.foo", msg)
js.PublishAsync("foo.bar", msg)
js.PublishAsync("foo.baz", msg)
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
sendRequestAndCheck := func(mreq *JSApiMsgGetRequest, numExpected int) {
t.Helper()
req, _ := json.Marshal(mreq)
// We will get multiple responses so can't do normal request.
reply := nats.NewInbox()
sub, err := nc.SubscribeSync(reply)
require_NoError(t, err)
defer sub.Unsubscribe()
err = nc.PublishRequest("$JS.API.DIRECT.GET.TEST", reply, req)
require_NoError(t, err)
// Make sure we get correct number of responses.
checkSubsPending(t, sub, numExpected)
}
// Total msg size being sent back to us.
msgSize := len(msg) + len("foo.foo")
// We should get 1 msg and 1 EOB
sendRequestAndCheck(&JSApiMsgGetRequest{Seq: 1, Batch: 3, MaxBytes: msgSize}, 2)
// Test NextFor tracks as well.
sendRequestAndCheck(&JSApiMsgGetRequest{Seq: 1, NextFor: "foo.bar", Batch: 3, MaxBytes: 2 * msgSize}, 3)
// Now test no MaxBytes to inherit server max_num_pending.
expected := (int(s.getOpts().MaxPending) / msgSize) + 1
sendRequestAndCheck(&JSApiMsgGetRequest{Seq: 1, Batch: 200}, expected+1)
}
func TestJetStreamMsgGetAsOfTime(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
AllowDirect: true,
})
require_NoError(t, err)
sendRequestAndCheck := func(mreq *JSApiMsgGetRequest, seq uint64, eerr error) {
t.Helper()
req, _ := json.Marshal(mreq)
rep, err := nc.Request(fmt.Sprintf(JSApiMsgGetT, "TEST"), req, time.Second)
require_NoError(t, err)
var mrep JSApiMsgGetResponse
err = json.Unmarshal(rep.Data, &mrep)
require_NoError(t, err)
if eerr != nil {
require_Error(t, mrep.ToError(), eerr)
return
}
require_NoError(t, mrep.ToError())
require_Equal(t, seq, mrep.Message.Sequence)
}
t0 := time.Now()
// Check for conflicting options.
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t0, LastFor: "foo.1"}, 0, NewJSBadRequestError())
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t0, Seq: 1}, 0, NewJSBadRequestError())
// Nothing exists yet in the stream.
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t0}, 0, NewJSNoMessageFoundError())
_, err = js.Publish("foo.1", nil)
require_NoError(t, err)
// Try again with t0 and now it will find the first message.
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t0}, 1, nil)
t1 := time.Now()
_, err = js.Publish("foo.2", nil)
require_NoError(t, err)
// At t0, first message will still be returned first.
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t0}, 1, nil)
// Unless we combine with NextFor...
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t0, NextFor: "foo.2"}, 2, nil)
// At t1 (after first message), the second message will be returned.
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t1}, 2, nil)
t2 := time.Now()
// t2 is later than the last message so nothing will be found.
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t2}, 0, NewJSNoMessageFoundError())
}
func TestJetStreamMsgDirectGetAsOfTime(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
AllowDirect: true,
})
require_NoError(t, err)
sendRequestAndCheck := func(mreq *JSApiMsgGetRequest, seq uint64, eerr string) {
t.Helper()
req, _ := json.Marshal(mreq)
rep, err := nc.Request(fmt.Sprintf(JSDirectMsgGetT, "TEST"), req, time.Second)
require_NoError(t, err)
if eerr != "" {
require_Equal(t, rep.Header.Get("Description"), eerr)
return
}
mseq, err := strconv.ParseUint(rep.Header.Get("Nats-Sequence"), 10, 64)
require_NoError(t, err)
require_Equal(t, seq, mseq)
}
t0 := time.Now()
// Check for conflicting options.
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t0, LastFor: "foo.1"}, 0, "Bad Request")
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t0, Seq: 1}, 0, "Bad Request")
// Nothing exists yet in the stream.
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t0}, 0, "Message Not Found")
_, err = js.Publish("foo.1", nil)
require_NoError(t, err)
// Try again with t0 and now it will find the first message.
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t0}, 1, "")
t1 := time.Now()
_, err = js.Publish("foo.2", nil)
require_NoError(t, err)
// At t0, first message will still be returned first.
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t0}, 1, "")
// Unless we combine with NextFor..
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t0, NextFor: "foo.2"}, 2, "")
// At t1 (after first message), the second message will be returned.
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t1}, 2, "")
t2 := time.Now()
// t2 is later than the last message so nothing will be found.
sendRequestAndCheck(&JSApiMsgGetRequest{StartTime: &t2}, 0, "Message Not Found")
}
func TestJetStreamSubjectFilteredPurgeClearsPendingAcks(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo", "bar"},
})
require_NoError(t, err)
for i := 0; i < 5; i++ {
js.Publish("foo", []byte("OK"))
js.Publish("bar", []byte("OK"))
}
// Note that there are no subject filters here, this is deliberate
// as previously the purge with filter code was checking for them.
// We want to prove that unfiltered consumers also get purged.
ci, err := js.AddConsumer("TEST", &nats.ConsumerConfig{
Name: "my_consumer",
AckPolicy: nats.AckExplicitPolicy,
MaxAckPending: 10,
})
require_NoError(t, err)
require_Equal(t, ci.NumPending, 10)
require_Equal(t, ci.NumAckPending, 0)
sub, err := js.PullSubscribe(">", "", nats.Bind("TEST", "my_consumer"))
require_NoError(t, err)
msgs, err := sub.Fetch(10)
require_NoError(t, err)
require_Len(t, len(msgs), 10)
ci, err = js.ConsumerInfo("TEST", "my_consumer")
require_NoError(t, err)
require_Equal(t, ci.NumPending, 0)
require_Equal(t, ci.NumAckPending, 10)
require_NoError(t, js.PurgeStream("TEST", &nats.StreamPurgeRequest{
Subject: "foo",
}))
ci, err = js.ConsumerInfo("TEST", "my_consumer")
require_NoError(t, err)
require_Equal(t, ci.NumPending, 0)
require_Equal(t, ci.NumAckPending, 5)
for i := 0; i < 5; i++ {
js.Publish("foo", []byte("OK"))
}
msgs, err = sub.Fetch(5)
require_NoError(t, err)
require_Len(t, len(msgs), 5)
ci, err = js.ConsumerInfo("TEST", "my_consumer")
require_NoError(t, err)
require_Equal(t, ci.NumPending, 0)
require_Equal(t, ci.NumAckPending, 10)
}
// Helper function for TestJetStreamConsumerPause*, TestJetStreamClusterConsumerPause*, TestJetStreamSuperClusterConsumerPause*
func jsTestPause_CreateOrUpdateConsumer(t *testing.T, nc *nats.Conn, action ConsumerAction, stream string, cc ConsumerConfig) *JSApiConsumerCreateResponse {
t.Helper()
j, err := json.Marshal(CreateConsumerRequest{
Stream: stream,
Config: cc,
Action: action,
})
require_NoError(t, err)
subj := fmt.Sprintf("$JS.API.CONSUMER.CREATE.%s.%s", stream, cc.Name)
m, err := nc.Request(subj, j, time.Second*3)
require_NoError(t, err)
var res JSApiConsumerCreateResponse
require_NoError(t, json.Unmarshal(m.Data, &res))
require_True(t, res.Config != nil)
return &res
}
// Helper function for TestJetStreamConsumerPause*, TestJetStreamClusterConsumerPause*, TestJetStreamSuperClusterConsumerPause*
func jsTestPause_PauseConsumer(t *testing.T, nc *nats.Conn, stream, consumer string, deadline time.Time) time.Time {
t.Helper()
j, err := json.Marshal(JSApiConsumerPauseRequest{
PauseUntil: deadline,
})
require_NoError(t, err)
subj := fmt.Sprintf("$JS.API.CONSUMER.PAUSE.%s.%s", stream, consumer)
msg, err := nc.Request(subj, j, time.Second)
require_NoError(t, err)
var res JSApiConsumerPauseResponse
require_NoError(t, json.Unmarshal(msg.Data, &res))
return res.PauseUntil
}
func TestJetStreamDirectGetMulti(t *testing.T) {
cases := []struct {
name string
cfg *nats.StreamConfig
}{
{name: "MemoryStore",
cfg: &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
AllowDirect: true,
Storage: nats.MemoryStorage,
}},
{name: "FileStore",
cfg: &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
AllowDirect: true,
}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(c.cfg)
require_NoError(t, err)
// Add in messages
for i := 0; i < 33; i++ {
js.PublishAsync("foo.foo", []byte(fmt.Sprintf("HELLO-%d", i)))
js.PublishAsync("foo.bar", []byte(fmt.Sprintf("WORLD-%d", i)))
js.PublishAsync("foo.baz", []byte(fmt.Sprintf("AGAIN-%d", i)))
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
// Direct subjects.
sendRequest := func(mreq *JSApiMsgGetRequest) *nats.Subscription {
t.Helper()
req, _ := json.Marshal(mreq)
// We will get multiple responses so can't do normal request.
reply := nats.NewInbox()
sub, err := nc.SubscribeSync(reply)
require_NoError(t, err)
err = nc.PublishRequest("$JS.API.DIRECT.GET.TEST", reply, req)
require_NoError(t, err)
return sub
}
// Subject / Sequence pair
type p struct {
subj string
seq int
}
var eob p
// Multi-Get will have a nil message as the end marker regardless.
checkResponses := func(sub *nats.Subscription, numPendingStart int, expected ...p) {
t.Helper()
last := "0"
defer sub.Unsubscribe()
checkSubsPending(t, sub, len(expected))
np := numPendingStart
for i := 0; i < len(expected); i++ {
msg, err := sub.NextMsg(10 * time.Millisecond)
require_NoError(t, err)
// If expected is _EMPTY_ that signals we expect a EOB marker.
if subj := expected[i].subj; subj != _EMPTY_ {
// Make sure subject is correct.
require_Equal(t, subj, msg.Header.Get(JSSubject))
// Make sure sequence is correct.
require_Equal(t, strconv.Itoa(expected[i].seq), msg.Header.Get(JSSequence))
// Should have Data field non-zero
require_True(t, len(msg.Data) > 0)
// Check we have NumPending and it's correct.
if np > 0 {
np--
}
require_Equal(t, strconv.Itoa(np), msg.Header.Get(JSNumPending))
require_Equal(t, last, msg.Header.Get(JSLastSequence))
last = msg.Header.Get(JSSequence)
} else {
// Check for properly formatted EOB marker.
// Should have no body.
require_Equal(t, len(msg.Data), 0)
// We mark status as 204 - No Content
require_Equal(t, msg.Header.Get("Status"), "204")
// Check description is EOB
require_Equal(t, msg.Header.Get("Description"), "EOB")
// Check we have NumPending and it's correct.
require_Equal(t, strconv.Itoa(np), msg.Header.Get(JSNumPending))
require_Equal(t, last, msg.Header.Get(JSLastSequence))
}
}
}
sub := sendRequest(&JSApiMsgGetRequest{MultiLastFor: []string{"foo.*"}})
checkResponses(sub, 3, p{"foo.foo", 97}, p{"foo.bar", 98}, p{"foo.baz", 99}, eob)
// Check with UpToSeq
sub = sendRequest(&JSApiMsgGetRequest{MultiLastFor: []string{"foo.*"}, UpToSeq: 3})
checkResponses(sub, 3, p{"foo.foo", 1}, p{"foo.bar", 2}, p{"foo.baz", 3}, eob)
// check last header sequence number is correct
sub = sendRequest(&JSApiMsgGetRequest{MultiLastFor: []string{"foo.foo", "foo.baz"}})
checkResponses(sub, 2, p{"foo.foo", 97}, p{"foo.baz", 99}, eob)
// Test No Results.
sub = sendRequest(&JSApiMsgGetRequest{MultiLastFor: []string{"bar.*"}})
checkSubsPending(t, sub, 1)
msg, err := sub.NextMsg(10 * time.Millisecond)
require_NoError(t, err)
// Check for properly formatted No Results.
// Should have no body.
require_Equal(t, len(msg.Data), 0)
// We mark status as 204 - No Content
require_Equal(t, msg.Header.Get("Status"), "404")
// Check description is No Results
require_Equal(t, msg.Header.Get("Description"), "No Results")
})
}
}
func TestJetStreamDirectGetMultiUpToTime(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
AllowDirect: true,
})
require_NoError(t, err)
js.Publish("foo.foo", []byte("1"))
js.Publish("foo.bar", []byte("1"))
js.Publish("foo.baz", []byte("1"))
start := time.Now()
time.Sleep(time.Second)
js.Publish("foo.foo", []byte("2"))
js.Publish("foo.bar", []byte("2"))
js.Publish("foo.baz", []byte("2"))
mid := time.Now()
time.Sleep(time.Second)
js.Publish("foo.foo", []byte("3"))
js.Publish("foo.bar", []byte("3"))
js.Publish("foo.baz", []byte("3"))
end := time.Now()
// Direct subjects.
sendRequest := func(mreq *JSApiMsgGetRequest) *nats.Subscription {
t.Helper()
req, _ := json.Marshal(mreq)
// We will get multiple responses so can't do normal request.
reply := nats.NewInbox()
sub, err := nc.SubscribeSync(reply)
require_NoError(t, err)
err = nc.PublishRequest("$JS.API.DIRECT.GET.TEST", reply, req)
require_NoError(t, err)
return sub
}
checkResponses := func(sub *nats.Subscription, val string, expected ...string) {
t.Helper()
defer sub.Unsubscribe()
checkSubsPending(t, sub, len(expected))
for i := 0; i < len(expected); i++ {
msg, err := sub.NextMsg(10 * time.Millisecond)
require_NoError(t, err)
// If expected is _EMPTY_ that signals we expect a EOB marker.
if subj := expected[i]; subj != _EMPTY_ {
// Make sure subject is correct.
require_Equal(t, subj, msg.Header.Get(JSSubject))
// Should have Data field non-zero
require_True(t, len(msg.Data) > 0)
// Make sure the value matches.
require_Equal(t, string(msg.Data), val)
}
}
}
// Make sure you can't set both.
sub := sendRequest(&JSApiMsgGetRequest{Seq: 1, MultiLastFor: []string{"foo.*"}, UpToSeq: 3, UpToTime: &start})
checkSubsPending(t, sub, 1)
msg, err := sub.NextMsg(10 * time.Millisecond)
require_NoError(t, err)
// Check for properly formatted No Results.
// Should have no body.
require_Equal(t, len(msg.Data), 0)
// We mark status as 204 - No Content
require_Equal(t, msg.Header.Get("Status"), "408")
// Check description is No Results
require_Equal(t, msg.Header.Get("Description"), "Bad Request")
// Valid responses.
sub = sendRequest(&JSApiMsgGetRequest{Seq: 1, MultiLastFor: []string{"foo.*"}, UpToTime: &start})
checkResponses(sub, "1", "foo.foo", "foo.bar", "foo.baz", _EMPTY_)
sub = sendRequest(&JSApiMsgGetRequest{Seq: 1, MultiLastFor: []string{"foo.*"}, UpToTime: &mid})
checkResponses(sub, "2", "foo.foo", "foo.bar", "foo.baz", _EMPTY_)
sub = sendRequest(&JSApiMsgGetRequest{Seq: 1, MultiLastFor: []string{"foo.*"}, UpToTime: &end})
checkResponses(sub, "3", "foo.foo", "foo.bar", "foo.baz", _EMPTY_)
}
func TestJetStreamDirectGetMultiMaxAllowed(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
AllowDirect: true,
})
require_NoError(t, err)
// from stream.go - const maxAllowedResponses = 1024, so max sure > 1024
// Add in messages
for i := 1; i <= 1025; i++ {
js.PublishAsync(fmt.Sprintf("foo.%d", i), []byte("OK"))
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
req, _ := json.Marshal(&JSApiMsgGetRequest{Seq: 1, MultiLastFor: []string{"foo.*"}})
msg, err := nc.Request("$JS.API.DIRECT.GET.TEST", req, time.Second)
require_NoError(t, err)
// Check for properly formatted Too Many Results error.
// Should have no body.
require_Equal(t, len(msg.Data), 0)
// We mark status as 413 - Too Many Results
require_Equal(t, msg.Header.Get("Status"), "413")
// Check description is No Results
require_Equal(t, msg.Header.Get("Description"), "Too Many Results")
}
func TestJetStreamDirectGetMultiPaging(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.*"},
AllowDirect: true,
})
require_NoError(t, err)
// We will queue up 500 messages, each 512k big and request them for a multi-get.
// This will not hit the max allowed limit of 1024, but will bump up against max bytes and only return partial results.
// We want to make sure we can pick up where we left off.
// Add in messages
data, sent := bytes.Repeat([]byte("Z"), 512*1024), 500
for i := 1; i <= sent; i++ {
js.PublishAsync(fmt.Sprintf("foo.%d", i), data)
}
select {
case <-js.PublishAsyncComplete():
case <-time.After(5 * time.Second):
t.Fatalf("Did not receive completion signal")
}
// Wait for all replicas to be correct.
time.Sleep(time.Second)
// Direct subjects.
sendRequest := func(mreq *JSApiMsgGetRequest) *nats.Subscription {
t.Helper()
req, _ := json.Marshal(mreq)
// We will get multiple responses so can't do normal request.
reply := nats.NewInbox()
sub, err := nc.SubscribeSync(reply)
require_NoError(t, err)
err = nc.PublishRequest("$JS.API.DIRECT.GET.TEST", reply, req)
require_NoError(t, err)
return sub
}
// Setup variables that control procesPartial
start, seq, np, b, bsz := 1, 1, sent, 0, 128
processPartial := func(expected int) {
t.Helper()
sub := sendRequest(&JSApiMsgGetRequest{Seq: uint64(start), Batch: b, MultiLastFor: []string{"foo.*"}})
checkSubsPending(t, sub, expected)
// Check partial.
// We should receive seqs seq-(seq+bsz-1)
for ; seq < start+(expected-1); seq++ {
msg, err := sub.NextMsg(10 * time.Millisecond)
require_NoError(t, err)
// Make sure sequence is correct.
require_Equal(t, strconv.Itoa(int(seq)), msg.Header.Get(JSSequence))
// Check we have NumPending and it's correct.
if np > 0 {
np--
}
require_Equal(t, strconv.Itoa(np), msg.Header.Get(JSNumPending))
}
// Now check EOB
msg, err := sub.NextMsg(10 * time.Millisecond)
require_NoError(t, err)
// We mark status as 204 - No Content
require_Equal(t, msg.Header.Get("Status"), "204")
// Check description is EOB
require_Equal(t, msg.Header.Get("Description"), "EOB")
// Check we have NumPending and it's correct.
require_Equal(t, strconv.Itoa(np), msg.Header.Get(JSNumPending))
// Check we have LastSequence and it's correct.
require_Equal(t, strconv.Itoa(seq-1), msg.Header.Get(JSLastSequence))
// Check we have UpToSequence and it's correct.
require_Equal(t, strconv.Itoa(sent), msg.Header.Get(JSUpToSequence))
// Update start
start = seq
}
processPartial(bsz + 1) // 128 + EOB
processPartial(bsz + 1) // 128 + EOB
processPartial(bsz + 1) // 128 + EOB
// Last one will be a partial block.
processPartial(116 + 1)
// Now reset and test that batch is honored as well.
start, seq, np, b = 1, 1, sent, 100
for i := 0; i < 5; i++ {
processPartial(b + 1) // 100 + EOB
}
}
// https://github.com/nats-io/nats-server/issues/4878
func TestJetStreamInterestStreamConsumerFilterEdit(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "INTEREST",
Retention: nats.InterestPolicy,
Subjects: []string{"interest.>"},
})
require_NoError(t, err)
_, err = js.AddConsumer("INTEREST", &nats.ConsumerConfig{
Durable: "C0",
FilterSubject: "interest.>",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
for i := 0; i < 10; i++ {
_, err = js.Publish(fmt.Sprintf("interest.%d", i), []byte(strconv.Itoa(i)))
require_NoError(t, err)
}
// we check we got 10 messages
nfo, err := js.StreamInfo("INTEREST")
require_NoError(t, err)
if nfo.State.Msgs != 10 {
t.Fatalf("expected 10 messages got %d", nfo.State.Msgs)
}
// now we lower the consumer interest from all subjects to 1,
// then check the stream state and check if interest behavior still works
_, err = js.UpdateConsumer("INTEREST", &nats.ConsumerConfig{
Durable: "C0",
FilterSubject: "interest.1",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
// we should now have only one message left
nfo, err = js.StreamInfo("INTEREST")
require_NoError(t, err)
if nfo.State.Msgs != 1 {
t.Fatalf("expected 1 message got %d", nfo.State.Msgs)
}
}
// https://github.com/nats-io/nats-server/issues/5383
func TestJetStreamInterestStreamWithFilterSubjectsConsumer(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "INTEREST",
Retention: nats.InterestPolicy,
Subjects: []string{"interest.>"},
})
require_NoError(t, err)
_, err = js.AddConsumer("INTEREST", &nats.ConsumerConfig{
Durable: "C",
FilterSubjects: []string{"interest.a", "interest.b"},
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
for _, sub := range []string{"interest.a", "interest.b", "interest.c"} {
_, err = js.Publish(sub, nil)
require_NoError(t, err)
}
// we check we got 2 messages, interest.c doesn't have interest
nfo, err := js.StreamInfo("INTEREST")
require_NoError(t, err)
if nfo.State.Msgs != 2 {
t.Fatalf("expected 2 messages got %d", nfo.State.Msgs)
}
}
func TestJetStreamAckAllWithLargeFirstSequenceAndNoAckFloor(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>"},
})
require_NoError(t, err)
// Set first sequence to something very big here. This shows the issue with AckAll the
// first time it is called and existing ack floor is 0.
err = js.PurgeStream("TEST", &nats.StreamPurgeRequest{Sequence: 10_000_000_000})
require_NoError(t, err)
// Now add in 100 msgs
for i := 0; i < 100; i++ {
js.Publish("foo.bar", []byte("hello"))
}
ss, err := js.PullSubscribe("foo.*", "C1", nats.AckAll())
require_NoError(t, err)
msgs, err := ss.Fetch(10, nats.MaxWait(100*time.Millisecond))
require_NoError(t, err)
require_Equal(t, len(msgs), 10)
start := time.Now()
msgs[9].AckSync()
if elapsed := time.Since(start); elapsed > 250*time.Millisecond {
t.Fatalf("AckSync took too long %v", elapsed)
}
// Make sure next fetch works right away with low timeout.
msgs, err = ss.Fetch(10, nats.MaxWait(100*time.Millisecond))
require_NoError(t, err)
require_Equal(t, len(msgs), 10)
_, err = js.StreamInfo("TEST", nats.MaxWait(250*time.Millisecond))
require_NoError(t, err)
// Now make sure that if we ack in the middle, meaning we still have ack pending,
// that we do the right thing as well.
ss, err = js.PullSubscribe("foo.*", "C2", nats.AckAll())
require_NoError(t, err)
msgs, err = ss.Fetch(10, nats.MaxWait(100*time.Millisecond))
require_NoError(t, err)
require_Equal(t, len(msgs), 10)
start = time.Now()
msgs[5].AckSync()
if elapsed := time.Since(start); elapsed > 250*time.Millisecond {
t.Fatalf("AckSync took too long %v", elapsed)
}
// Make sure next fetch works right away with low timeout.
msgs, err = ss.Fetch(10, nats.MaxWait(100*time.Millisecond))
require_NoError(t, err)
require_Equal(t, len(msgs), 10)
_, err = js.StreamInfo("TEST", nats.MaxWait(250*time.Millisecond))
require_NoError(t, err)
}
func TestJetStreamAckAllWithLargeFirstSequenceAndNoAckFloorWithInterestPolicy(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>"},
Retention: nats.InterestPolicy,
})
require_NoError(t, err)
// Set first sequence to something very big here. This shows the issue with AckAll the
// first time it is called and existing ack floor is 0.
err = js.PurgeStream("TEST", &nats.StreamPurgeRequest{Sequence: 10_000_000_000})
require_NoError(t, err)
ss, err := js.PullSubscribe("foo.*", "C1", nats.AckAll())
require_NoError(t, err)
// Now add in 100 msgs
for i := 0; i < 100; i++ {
js.Publish("foo.bar", []byte("hello"))
}
msgs, err := ss.Fetch(10, nats.MaxWait(100*time.Millisecond))
require_NoError(t, err)
require_Equal(t, len(msgs), 10)
start := time.Now()
msgs[5].AckSync()
if elapsed := time.Since(start); elapsed > 250*time.Millisecond {
t.Fatalf("AckSync took too long %v", elapsed)
}
// We are testing for run away loops acking messages in the stream that are not there.
_, err = js.StreamInfo("TEST", nats.MaxWait(100*time.Millisecond))
require_NoError(t, err)
}
// Allow streams with $JS or $SYS prefixes for audit purposes but require no pub ack be set.
func TestJetStreamAuditStreams(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
jsOverlap := errors.New("subjects that overlap with jetstream api require no-ack to be true")
sysOverlap := errors.New("subjects that overlap with system api require no-ack to be true")
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"$JS.>"},
})
require_Error(t, err, NewJSStreamInvalidConfigError(jsOverlap))
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"$JS.API.>"},
})
require_Error(t, err, NewJSStreamInvalidConfigError(jsOverlap))
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"$JSC.>"},
})
require_Error(t, err, NewJSStreamInvalidConfigError(jsOverlap))
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"$SYS.>"},
})
require_Error(t, err, NewJSStreamInvalidConfigError(sysOverlap))
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{">"},
})
require_Error(t, err, NewJSStreamInvalidConfigError(errors.New("capturing all subjects requires no-ack to be true")))
// These should all be ok if no pub ack.
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST1",
Subjects: []string{"$JS.>"},
NoAck: true,
})
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST2",
Subjects: []string{"$JSC.>"},
NoAck: true,
})
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST3",
Subjects: []string{"$SYS.>"},
NoAck: true,
})
require_NoError(t, err)
// Since prior behavior did allow $JS.EVENT to be captured without no-ack, these might break
// on a server upgrade so make sure they still work ok without --no-ack.
// To avoid overlap error.
err = js.DeleteStream("TEST1")
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST4",
Subjects: []string{"$JS.EVENT.>"},
})
require_NoError(t, err)
// Also allow $SYS.ACCOUNT to be captured without no-ack, these also might break
// on a server upgrade so make sure they still work ok without --no-ack.
// To avoid overlap error.
err = js.DeleteStream("TEST3")
require_NoError(t, err)
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST5",
Subjects: []string{"$SYS.ACCOUNT.>"},
})
require_NoError(t, err)
// We will test handling of ">" on a cluster here.
// Specific test for capturing everything which will require both no-ack and replicas of 1.
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{">"},
})
require_Error(t, err, NewJSStreamInvalidConfigError(errors.New("capturing all subjects requires no-ack to be true")))
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{">"},
Replicas: 3,
NoAck: true,
})
require_Error(t, err, NewJSStreamInvalidConfigError(errors.New("capturing all subjects requires replicas of 1")))
// Ths should work ok.
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{">"},
Replicas: 1,
NoAck: true,
})
require_NoError(t, err)
}
// https://github.com/nats-io/nats-server/issues/5570
func TestJetStreamBadSubjectMappingStream(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
// Client for API requests.
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "test"})
require_NoError(t, err)
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "test",
Sources: []*nats.StreamSource{
{
Name: "mapping",
SubjectTransforms: []nats.SubjectTransformConfig{
{
Source: "events.*",
Destination: "events.{{wildcard(1)}}{{split(3,1)}}",
},
},
},
},
})
require_Error(t, err, NewJSStreamUpdateError(errors.New("nats: source transform: invalid mapping destination: too many arguments passed to the function in {{wildcard(1)}}{{split(3,1)}}")))
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "test",
Sources: []*nats.StreamSource{
{
Name: "mapping",
SubjectTransforms: []nats.SubjectTransformConfig{
{
Source: "events.>.*",
Destination: "events.{{split(1,1)}}",
},
},
},
},
})
require_Error(t, err, NewJSStreamUpdateError(errors.New("nats: source transform source: invalid subject events.>.*")))
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "test",
Mirror: &nats.StreamSource{
Name: "mapping",
SubjectTransforms: []nats.SubjectTransformConfig{
{
Source: "events.*",
Destination: "events.{{split(3,1)}}",
},
},
},
})
require_Error(t, err, NewJSStreamUpdateError(errors.New("nats: mirror transform: invalid mapping destination: wildcard index out of range in {{split(3,1)}}: [3]")))
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "test",
Mirror: &nats.StreamSource{
Name: "mapping",
SubjectTransforms: []nats.SubjectTransformConfig{
{
Source: "events.>.*",
Destination: "events.{{split(1,1)}}",
},
},
},
})
require_Error(t, err, NewJSStreamUpdateError(errors.New("nats: mirror transform source: invalid subject events.>.*")))
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "test",
SubjectTransform: &nats.SubjectTransformConfig{
Source: "events.*",
Destination: "events.{{split(3,1)}}",
},
})
require_Error(t, err, NewJSStreamUpdateError(errors.New("nats: stream transform: invalid mapping destination: wildcard index out of range in {{split(3,1)}}: [3]")))
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "test",
SubjectTransform: &nats.SubjectTransformConfig{
Source: "events.>.*",
Destination: "events.{{split(1,1)}}",
},
})
require_Error(t, err, NewJSStreamUpdateError(errors.New("nats: stream transform source: invalid subject events.>.*")))
}
func TestJetStreamInterestStreamWithDuplicateMessages(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "INTEREST",
Subjects: []string{"interest"},
Replicas: 1,
Retention: nats.InterestPolicy,
}
_, err := js.AddStream(cfg)
require_NoError(t, err)
// Publishing the first time should give a sequence, even when there's no interest.
pa, err := js.Publish("interest", nil, nats.MsgId("dedupe"))
require_NoError(t, err)
require_Equal(t, pa.Sequence, 1)
require_Equal(t, pa.Duplicate, false)
// Publishing a duplicate with no interest should return the same sequence as above.
pa, err = js.Publish("interest", nil, nats.MsgId("dedupe"))
require_NoError(t, err)
require_Equal(t, pa.Sequence, 1)
require_Equal(t, pa.Duplicate, true)
}
func TestJetStreamStreamCreatePedanticMode(t *testing.T) {
cfgFmt := []byte(fmt.Sprintf(`
jetstream: {
enabled: true
max_file_store: 100MB
store_dir: %s
limits: {duplicate_window: "1m", max_request_batch: 250}
}
accounts: {
myacc: {
jetstream: enabled
users: [ { user: user, password: pass } ]
}
}
no_auth_user: user
`, t.TempDir()))
conf := createConfFile(t, cfgFmt)
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
tests := []struct {
name string
cfg StreamConfigRequest
shouldErr bool
update bool
}{
{
name: "too_high_duplicate",
cfg: StreamConfigRequest{
StreamConfig: StreamConfig{
Name: "TEST",
MaxAge: time.Minute,
Duplicates: time.Hour,
Storage: FileStorage,
},
Pedantic: true,
},
shouldErr: true,
},
{
name: "duplicate_over_limits",
cfg: StreamConfigRequest{
StreamConfig: StreamConfig{
Name: "TEST",
MaxAge: time.Hour * 60,
Duplicates: time.Hour,
Storage: FileStorage,
},
Pedantic: false,
},
shouldErr: true,
},
{
name: "duplicate_window_within_limits",
cfg: StreamConfigRequest{
StreamConfig: StreamConfig{
Name: "TEST",
MaxAge: time.Hour * 60,
Duplicates: time.Second * 30,
Storage: FileStorage,
},
Pedantic: false,
},
shouldErr: false,
},
{
name: "update_too_high_duplicate",
cfg: StreamConfigRequest{
StreamConfig: StreamConfig{
Name: "TEST",
MaxAge: time.Minute,
Duplicates: time.Hour,
Storage: FileStorage,
},
Pedantic: true,
},
update: true,
shouldErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if !test.update {
_, err := addStreamPedanticWithError(t, nc, &test.cfg)
require_True(t, (err != nil) == test.shouldErr)
} else {
_, err := updateStreamPedanticWithError(t, nc, &test.cfg)
require_True(t, (err != nil) == test.shouldErr)
}
})
}
}
func TestJetStreamStrictMode(t *testing.T) {
cfgFmt := []byte(fmt.Sprintf(`
jetstream: {
strict: true
enabled: true
max_file_store: 100MB
store_dir: %s
limits: {duplicate_window: "1m", max_request_batch: 250}
}
`, t.TempDir()))
conf := createConfFile(t, cfgFmt)
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
nc, err := nats.Connect(s.ClientURL())
if err != nil {
t.Fatalf("Error connecting to NATS: %v", err)
}
defer nc.Close()
tests := []struct {
name string
subject string
payload []byte
expectedErr string
}{
{
name: "Stream Create",
subject: "$JS.API.STREAM.CREATE.TEST_STREAM",
payload: []byte(`{"name":"TEST_STREAM","subjects":["test.>"],"extra_field":"unexpected"}`),
expectedErr: "invalid JSON",
},
{
name: "Stream Update",
subject: "$JS.API.STREAM.UPDATE.TEST_STREAM",
payload: []byte(`{"name":"TEST_STREAM","subjects":["test.>"],"extra_field":"unexpected"}`),
expectedErr: "invalid JSON",
},
{
name: "Stream Delete",
subject: "$JS.API.STREAM.DELETE.TEST_STREAM",
payload: []byte(`{"extra_field":"unexpected"}`),
expectedErr: "expected an empty request payload",
},
{
name: "Stream Info",
subject: "$JS.API.STREAM.INFO.TEST_STREAM",
payload: []byte(`{"extra_field":"unexpected"}`),
expectedErr: "invalid JSON",
},
{
name: "Consumer Create",
subject: "$JS.API.CONSUMER.CREATE.TEST_STREAM.TEST_CONSUMER",
payload: []byte(`{"durable_name":"TEST_CONSUMER","ack_policy":"explicit","extra_field":"unexpected"}`),
expectedErr: "invalid JSON",
},
{
name: "Consumer Delete",
subject: "$JS.API.CONSUMER.DELETE.TEST_STREAM.TEST_CONSUMER",
payload: []byte(`{"extra_field":"unexpected"}`),
expectedErr: "expected an empty request payload",
},
{
name: "Consumer Info",
subject: "$JS.API.CONSUMER.INFO.TEST_STREAM.TEST_CONSUMER",
payload: []byte(`{"extra_field":"unexpected"}`),
expectedErr: "expected an empty request payload",
},
{
name: "Stream List",
subject: "$JS.API.STREAM.LIST",
payload: []byte(`{"extra_field":"unexpected"}`),
expectedErr: "invalid JSON",
},
{
name: "Consumer List",
subject: "$JS.API.CONSUMER.LIST.TEST_STREAM",
payload: []byte(`{"extra_field":"unexpected"}`),
expectedErr: "invalid JSON",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resp, err := nc.Request(tt.subject, tt.payload, time.Second*10)
if err != nil {
t.Fatalf("Request failed: %v", err)
}
var apiResp ApiResponse = ApiResponse{}
if err := json.Unmarshal(resp.Data, &apiResp); err != nil {
t.Fatalf("Error unmarshalling response: %v", err)
}
require_NotNil(t, apiResp.Error.Description)
require_Contains(t, apiResp.Error.Description, tt.expectedErr)
})
}
}
func addConsumerWithError(t *testing.T, nc *nats.Conn, cfg *CreateConsumerRequest) (*ConsumerInfo, *ApiError) {
t.Helper()
req, err := json.Marshal(cfg)
require_NoError(t, err)
rmsg, err := nc.Request(fmt.Sprintf(JSApiDurableCreateT, cfg.Stream, cfg.Config.Durable), req, 5*time.Second)
require_NoError(t, err)
var resp JSApiConsumerCreateResponse
err = json.Unmarshal(rmsg.Data, &resp)
require_NoError(t, err)
if resp.Type != JSApiConsumerCreateResponseType {
t.Fatalf("Invalid response type %s expected %s", resp.Type, JSApiConsumerCreateResponseType)
}
return resp.ConsumerInfo, resp.Error
}
func TestJetStreamSourceRemovalAndReAdd(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// The source stream.
_, err := js.AddStream(&nats.StreamConfig{
Name: "SRC",
Subjects: []string{"foo.*"},
})
require_NoError(t, err)
// The stream that sources.
_, err = js.AddStream(&nats.StreamConfig{
Name: "TEST",
Sources: []*nats.StreamSource{{
Name: "SRC",
}},
})
require_NoError(t, err)
// Now add in 10 msgs.
for i := 0; i < 10; i++ {
_, err := js.Publish(fmt.Sprintf("foo.%d", i), []byte("test"))
require_NoError(t, err)
}
// Make sure we have 10 msgs in TEST.
checkFor(t, time.Second, 200*time.Millisecond, func() error {
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
if si.State.Msgs == 10 {
return nil
}
return fmt.Errorf("Do not have all msgs yet, %d of 10", si.State.Msgs)
})
// Now update the TEST stream to no longer source.
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "TEST",
})
require_NoError(t, err)
// Now add in 10 more msgs.
for i := 0; i < 10; i++ {
_, err := js.Publish(fmt.Sprintf("foo.%d", i+10), []byte("test"))
require_NoError(t, err)
}
// Make sure we are still stuck at 10 for TEST.
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 10)
// Now re-add the source to our stream.
_, err = js.UpdateStream(&nats.StreamConfig{
Name: "TEST",
Sources: []*nats.StreamSource{{
Name: "SRC",
}},
})
require_NoError(t, err)
// Make sure we have 20 msgs now.
// Make sure we have 10 msgs in TEST.
checkFor(t, time.Second, 200*time.Millisecond, func() error {
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
if si.State.Msgs == 20 {
return nil
}
return fmt.Errorf("Do not have all msgs yet, %d of 20", si.State.Msgs)
})
// Check that we get what we want in the stream.
sub, err := js.PullSubscribe("foo.*", "d")
require_NoError(t, err)
msgs, err := sub.Fetch(20)
require_NoError(t, err)
require_Equal(t, len(msgs), 20)
for i, m := range msgs {
require_Equal(t, m.Subject, fmt.Sprintf("foo.%d", i))
}
}
func TestJetStreamRateLimitHighStreamIngest(t *testing.T) {
cfgFmt := []byte(fmt.Sprintf(`
jetstream: {
enabled: true
store_dir: %s
max_buffered_size: 1kb
max_buffered_msgs: 1
}
`, t.TempDir()))
conf := createConfFile(t, cfgFmt)
s, opts := RunServerWithConfig(conf)
defer s.Shutdown()
require_Equal(t, opts.StreamMaxBufferedSize, 1024)
require_Equal(t, opts.StreamMaxBufferedMsgs, 1)
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"test"},
})
require_NoError(t, err)
// Create a reply inbox that we can await API requests on.
// This is instead of using nc.Request().
inbox := nc.NewRespInbox()
resp := make(chan *nats.Msg, 1000)
_, err = nc.ChanSubscribe(inbox, resp)
require_NoError(t, err)
// Publish a large number of messages using Core NATS withou
// waiting for the responses from the API.
msg := &nats.Msg{
Subject: "test",
Reply: inbox,
}
for i := 0; i < 1000; i++ {
require_NoError(t, nc.PublishMsg(msg))
}
// Now sort through the API responses. We're looking for one
// that tells us that we were rate-limited. If we don't find
// one then we fail the test.
var rateLimited bool
for i, msg := 0, <-resp; i < 1000; i, msg = i+1, <-resp {
if msg.Header.Get("Status") == "429" {
rateLimited = true
break
}
}
require_True(t, rateLimited)
}
func TestJetStreamRateLimitHighStreamIngestDefaults(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"test"},
})
require_NoError(t, err)
stream, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
require_Equal(t, stream.msgs.mlen, streamDefaultMaxQueueMsgs)
require_Equal(t, stream.msgs.msz, streamDefaultMaxQueueBytes)
}
func TestJetStreamStreamConfigClone(t *testing.T) {
cfg := &StreamConfig{
Name: "name",
Placement: &Placement{Cluster: "placement", Tags: []string{"tag"}},
Mirror: &StreamSource{Name: "mirror"},
Sources: []*StreamSource{&StreamSource{Name: "source"}},
SubjectTransform: &SubjectTransformConfig{Source: "source", Destination: "dest"},
RePublish: &RePublish{Source: "source", Destination: "dest", HeadersOnly: false},
Metadata: make(map[string]string),
}
// Copy should be complete.
clone := cfg.clone()
require_True(t, reflect.DeepEqual(cfg, clone))
// Changing fields should not update the original.
clone.Placement.Cluster = "diff"
require_False(t, reflect.DeepEqual(cfg.Placement, clone.Placement))
clone.Mirror.Name = "diff"
require_False(t, reflect.DeepEqual(cfg.Mirror, clone.Mirror))
clone.Sources[0].Name = "diff"
require_False(t, reflect.DeepEqual(cfg.Sources, clone.Sources))
clone.SubjectTransform.Source = "diff"
require_False(t, reflect.DeepEqual(cfg.SubjectTransform, clone.SubjectTransform))
clone.RePublish.Source = "diff"
require_False(t, reflect.DeepEqual(cfg.RePublish, clone.RePublish))
clone.Metadata["key"] = "value"
require_False(t, reflect.DeepEqual(cfg.Metadata, clone.Metadata))
}
func TestIsJSONObjectOrArray(t *testing.T) {
tests := []struct {
name string
data []byte
valid bool
}{
{"empty", []byte{}, false},
{"empty_object", []byte("{}"), true},
{"empty object, not trimmed", []byte("\t\n\r{ }"), true},
{"empty_array", []byte("[]"), true},
{"empty array, not trimmed", []byte("\t\n\r[ ]"), true},
// This is a valid JSON, but it's not a JSON object or array.
{"empty_string", []byte("\"\""), false},
{"whitespace_only", []byte(" "), false},
{"object_with_whitespace", []byte("{ }"), true},
{"array_with_whitespace", []byte("[ ]"), true},
{"string_with_whitespace", []byte(" \"text\""), false},
{"number", []byte("123"), false},
{"boolean_true", []byte("true"), false},
{"boolean_false", []byte("false"), false},
{"null_value", []byte("null"), false}, {"invalid JSON", []byte("invalid"), false},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require_Equal(t, isJSONObjectOrArray(test.data), test.valid)
})
}
}
func TestJetStreamSourcingClipStartSeq(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "ORIGIN",
Subjects: []string{"test"},
})
require_NoError(t, err)
for i := 0; i < 10; i++ {
_, err := js.Publish("test", nil)
require_NoError(t, err)
}
_, err = js.AddStream(&nats.StreamConfig{
Name: "SOURCING",
Sources: []*nats.StreamSource{
{
Name: "ORIGIN",
OptStartSeq: 20,
},
},
})
require_NoError(t, err)
// Wait for sourcing consumer to be created.
time.Sleep(time.Second)
mset, err := s.GlobalAccount().lookupStream("ORIGIN")
require_NoError(t, err)
require_True(t, mset != nil)
require_Len(t, len(mset.consumers), 1)
for _, o := range mset.consumers {
// Should have been clipped back to below 20 as only
// 10 messages in the origin stream.
require_Equal(t, o.sseq, 11)
}
}
func TestJetStreamMirroringClipStartSeq(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "ORIGIN",
Subjects: []string{"test"},
})
require_NoError(t, err)
for i := 0; i < 10; i++ {
_, err := js.Publish("test", nil)
require_NoError(t, err)
}
_, err = js.AddStream(&nats.StreamConfig{
Name: "MIRRORING",
Mirror: &nats.StreamSource{
Name: "ORIGIN",
OptStartSeq: 20,
},
})
require_NoError(t, err)
// Wait for mirroring consumer to be created.
time.Sleep(time.Second)
mset, err := s.GlobalAccount().lookupStream("ORIGIN")
require_NoError(t, err)
require_True(t, mset != nil)
require_Len(t, len(mset.consumers), 1)
for _, o := range mset.consumers {
// Should have been clipped back to below 20 as only
// 10 messages in the origin stream.
require_Equal(t, o.sseq, 11)
}
}
func TestJetStreamDelayedAPIResponses(t *testing.T) {
tdir := t.TempDir()
tmpl := `
listen: 127.0.0.1:-1
%sjetstream: {max_mem_store: 64GB, max_file_store: 10TB, store_dir: %q}
`
conf := createConfFile(t, []byte(fmt.Sprintf(tmpl, "", tdir)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
nc := natsConnect(t, s.ClientURL())
defer nc.Close()
sub := natsSubSync(t, nc, JSAuditAdvisory)
acc := s.GlobalAccount()
// Send B, A, D, C and expected to receive A, B, C, D
s.sendDelayedAPIErrResponse(nil, acc, "B", _EMPTY_, "request2", "response2", nil, 500*time.Millisecond)
time.Sleep(50 * time.Millisecond)
s.sendDelayedAPIErrResponse(nil, acc, "A", _EMPTY_, "request1", "response1", nil, 200*time.Millisecond)
time.Sleep(50 * time.Millisecond)
s.sendDelayedAPIErrResponse(nil, acc, "D", _EMPTY_, "request4", "response4", nil, 800*time.Millisecond)
time.Sleep(50 * time.Millisecond)
s.sendDelayedAPIErrResponse(nil, acc, "C", _EMPTY_, "request3", "response3", nil, 650*time.Millisecond)
check := func(req, resp string) {
t.Helper()
msg := natsNexMsg(t, sub, time.Second)
var audit JSAPIAudit
err := json.Unmarshal(msg.Data, &audit)
require_NoError(t, err)
require_Equal(t, audit.Request, req)
require_Equal(t, audit.Response, resp)
}
check("request1", "response1")
check("request2", "response2")
check("request3", "response3")
check("request4", "response4")
// Verify that if a raft group is canceled, the delayed API response is canceled too.
node := &raft{quit: make(chan struct{})}
g := &raftGroup{node: node}
// Send delayed API response with this raft group
s.sendDelayedAPIErrResponse(nil, acc, "E", _EMPTY_, "request5", "response5", g, 250*time.Millisecond)
time.Sleep(50 * time.Millisecond)
// Send that one without a group
s.sendDelayedAPIErrResponse(nil, acc, "F", _EMPTY_, "request6", "response6", nil, 400*time.Millisecond)
// Close the "request5"'s channel.
close(node.quit)
// So we should receive request6, not 5.
check("request6", "response6")
// Check config reload.
s.sendDelayedAPIErrResponse(nil, acc, "G", _EMPTY_, "request7", "response7", nil, 400*time.Millisecond)
time.Sleep(50 * time.Millisecond)
// Send a bunch more
for i := 0; i < 10; i++ {
s.sendDelayedAPIErrResponse(nil, acc, "H", _EMPTY_, "request8", "response8", nil, 500*time.Millisecond)
}
// Config reload to disable JS.
reloadUpdateConfig(t, s, conf, fmt.Sprintf(tmpl, "#", tdir))
check("request7", "response7")
// We should not receive more.
if msg, err := sub.NextMsg(600 * time.Millisecond); err != nats.ErrTimeout {
t.Fatalf("Did not expect to receive: %s", msg.Data)
}
// Check that the queue is empty.
s.mu.RLock()
q := s.delayedAPIResponses
s.mu.RUnlock()
require_Equal(t, q.len(), 0)
// Restore JS and check delayed response can be received.
reloadUpdateConfig(t, s, conf, fmt.Sprintf(tmpl, "", tdir))
// Wait until js is re-enabled
checkFor(t, 10*time.Second, 50*time.Millisecond, func() error {
if s.getJetStream() == nil {
return ErrJetStreamNotEnabled
}
return nil
})
s.sendDelayedAPIErrResponse(nil, acc, "I", _EMPTY_, "request9", "response9", nil, 100*time.Millisecond)
check("request9", "response9")
}
func TestJetStreamMemoryPurgeClearsSubjectsState(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: nats.MemoryStorage,
})
require_NoError(t, err)
pa, err := js.Publish("foo", nil)
require_NoError(t, err)
require_Equal(t, pa.Sequence, 1)
// When requesting stream info, we expect one subject foo with one entry.
si, err := js.StreamInfo("TEST", &nats.StreamInfoRequest{SubjectsFilter: ">"})
require_NoError(t, err)
require_Len(t, len(si.State.Subjects), 1)
require_Equal(t, si.State.Subjects["foo"], 1)
// After purging, moving the sequence up, the subjects state should be cleared.
err = js.PurgeStream("TEST", &nats.StreamPurgeRequest{Sequence: 100})
require_NoError(t, err)
si, err = js.StreamInfo("TEST", &nats.StreamInfoRequest{SubjectsFilter: ">"})
require_NoError(t, err)
require_Len(t, len(si.State.Subjects), 0)
}
func TestJetStreamWouldExceedLimits(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
js := s.getJetStream()
require_NotNil(t, js)
// Storing exactly up to the limit should work.
require_False(t, js.wouldExceedLimits(MemoryStorage, int(js.config.MaxMemory)))
require_False(t, js.wouldExceedLimits(FileStorage, int(js.config.MaxStore)))
// Storing one more than the max should exceed limits.
require_True(t, js.wouldExceedLimits(MemoryStorage, int(js.config.MaxMemory)+1))
require_True(t, js.wouldExceedLimits(FileStorage, int(js.config.MaxStore)+1))
}
func TestJetStreamMessageTTL(t *testing.T) {
for _, storage := range []StorageType{FileStorage, MemoryStorage} {
t.Run(storage.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Storage: storage,
Subjects: []string{"test"},
AllowMsgTTL: true,
})
msg := &nats.Msg{
Subject: "test",
Header: nats.Header{},
}
for i := 1; i <= 10; i++ {
msg.Header.Set(JSMessageTTL, "1s")
_, err := js.PublishMsg(msg)
require_NoError(t, err)
}
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 10)
require_Equal(t, si.State.FirstSeq, 1)
require_Equal(t, si.State.LastSeq, 10)
time.Sleep(time.Second * 2)
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 0)
require_Equal(t, si.State.FirstSeq, 11)
require_Equal(t, si.State.LastSeq, 10)
})
}
}
func TestJetStreamMessageTTLRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Storage: FileStorage,
Subjects: []string{"test"},
AllowMsgTTL: true,
})
msg := &nats.Msg{
Subject: "test",
Header: nats.Header{},
}
for i := 1; i <= 10; i++ {
msg.Header.Set(JSMessageTTL, "1s")
_, err := js.PublishMsg(msg)
require_NoError(t, err)
}
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 10)
require_Equal(t, si.State.FirstSeq, 1)
require_Equal(t, si.State.LastSeq, 10)
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 10)
require_Equal(t, si.State.FirstSeq, 1)
require_Equal(t, si.State.LastSeq, 10)
time.Sleep(time.Second * 2)
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 0)
require_Equal(t, si.State.FirstSeq, 11)
require_Equal(t, si.State.LastSeq, 10)
}
func TestJetStreamMessageTTLRecovered(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Storage: FileStorage,
Subjects: []string{"test"},
AllowMsgTTL: true,
})
msg := &nats.Msg{
Subject: "test",
Header: nats.Header{},
}
for i := 1; i <= 10; i++ {
msg.Header.Set(JSMessageTTL, "1s")
_, err := js.PublishMsg(msg)
require_NoError(t, err)
}
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 10)
require_Equal(t, si.State.FirstSeq, 1)
require_Equal(t, si.State.LastSeq, 10)
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
fn := filepath.Join(sd, globalAccountName, streamsDir, "TEST", msgDir, ttlStreamStateFile)
require_NoError(t, os.RemoveAll(fn))
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 10)
require_Equal(t, si.State.FirstSeq, 1)
require_Equal(t, si.State.LastSeq, 10)
time.Sleep(time.Second * 2)
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 0)
require_Equal(t, si.State.FirstSeq, 11)
require_Equal(t, si.State.LastSeq, 10)
}
func TestJetStreamMessageTTLInvalid(t *testing.T) {
for _, storage := range []StorageType{FileStorage, MemoryStorage} {
t.Run(storage.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Storage: storage,
Subjects: []string{"test"},
AllowMsgTTL: true,
})
msg := &nats.Msg{
Subject: "test",
Header: nats.Header{},
}
msg.Header.Set(JSMessageTTL, "500ms")
_, err := js.PublishMsg(msg)
require_Error(t, err)
msg.Header.Set(JSMessageTTL, "something")
_, err = js.PublishMsg(msg)
require_Error(t, err)
})
}
}
func TestJetStreamMessageTTLNotUpdatable(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Storage: FileStorage,
Subjects: []string{"test"},
AllowMsgTTL: true,
})
_, err := jsStreamUpdate(t, nc, &StreamConfig{
Name: "TEST",
Storage: FileStorage,
Subjects: []string{"test"},
AllowMsgTTL: false,
})
require_Error(t, err)
}
func TestJetStreamMessageTTLNeverExpire(t *testing.T) {
for _, storage := range []StorageType{FileStorage, MemoryStorage} {
t.Run(storage.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Storage: storage,
Subjects: []string{"test"},
AllowMsgTTL: true,
MaxAge: time.Second,
})
msg := &nats.Msg{
Subject: "test",
Header: nats.Header{},
}
// The first message we publish is set to "never expire", therefore it
// won't age out with the MaxAge policy.
msg.Header.Set(JSMessageTTL, "never")
_, err := js.PublishMsg(msg)
require_NoError(t, err)
// Following messages will be published as normal and will age out.
msg.Header.Del(JSMessageTTL)
for i := 1; i <= 10; i++ {
_, err := js.PublishMsg(msg)
require_NoError(t, err)
}
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 11)
require_Equal(t, si.State.FirstSeq, 1)
require_Equal(t, si.State.LastSeq, 11)
time.Sleep(time.Second * 2)
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 1)
require_Equal(t, si.State.FirstSeq, 1)
require_Equal(t, si.State.LastSeq, 11)
})
}
}
func TestJetStreamMessageTTLDisabled(t *testing.T) {
for _, storage := range []StorageType{FileStorage, MemoryStorage} {
t.Run(storage.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Storage: storage,
Subjects: []string{"test"},
})
msg := &nats.Msg{
Subject: "test",
Header: nats.Header{},
}
msg.Header.Set(JSMessageTTL, "1s")
_, err := js.PublishMsg(msg)
require_Error(t, err)
})
}
}
func TestJetStreamMessageTTLWhenSourcing(t *testing.T) {
for _, storage := range []StorageType{FileStorage, MemoryStorage} {
t.Run(storage.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "Origin",
Storage: storage,
Subjects: []string{"test"},
AllowMsgTTL: true,
})
require_NoError(t, err)
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "TTLEnabled",
Storage: storage,
Sources: []*StreamSource{
{Name: "Origin"},
},
AllowMsgTTL: true,
})
require_NoError(t, err)
ttlDisabledConfig := &StreamConfig{
Name: "TTLDisabled",
Storage: storage,
Sources: []*StreamSource{
{Name: "Origin"},
},
AllowMsgTTL: false,
}
_, err = jsStreamCreate(t, nc, ttlDisabledConfig)
require_NoError(t, err)
hdr := nats.Header{}
hdr.Add(JSMessageTTL, "1s")
_, err = js.PublishMsg(&nats.Msg{
Subject: "test",
Header: hdr,
})
require_NoError(t, err)
for _, stream := range []string{"TTLEnabled", "TTLDisabled"} {
t.Run(stream, func(t *testing.T) {
sc, err := js.PullSubscribe("test", "consumer", nats.BindStream(stream))
require_NoError(t, err)
msgs, err := sc.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_Equal(t, msgs[0].Header.Get(JSMessageTTL), "1s")
time.Sleep(time.Second)
si, err := js.StreamInfo(stream)
require_NoError(t, err)
if stream != "TTLDisabled" {
require_Equal(t, si.State.Msgs, 0)
return
}
require_Equal(t, si.State.Msgs, 1)
ttlDisabledConfig.AllowMsgTTL = true
_, err = jsStreamUpdate(t, nc, ttlDisabledConfig)
require_NoError(t, err)
si, err = js.StreamInfo(stream)
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 0)
})
}
})
}
}
func TestJetStreamMessageTTLWhenMirroring(t *testing.T) {
for _, storage := range []StorageType{FileStorage, MemoryStorage} {
t.Run(storage.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "Origin",
Storage: storage,
Subjects: []string{"test"},
AllowMsgTTL: true,
})
require_NoError(t, err)
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "TTLEnabled",
Storage: storage,
Mirror: &StreamSource{
Name: "Origin",
},
AllowMsgTTL: true,
})
require_NoError(t, err)
ttlDisabledConfig := &StreamConfig{
Name: "TTLDisabled",
Storage: storage,
Mirror: &StreamSource{
Name: "Origin",
},
AllowMsgTTL: false,
}
_, err = jsStreamCreate(t, nc, ttlDisabledConfig)
require_NoError(t, err)
hdr := nats.Header{}
hdr.Add(JSMessageTTL, "1s")
_, err = js.PublishMsg(&nats.Msg{
Subject: "test",
Header: hdr,
})
require_NoError(t, err)
for _, stream := range []string{"TTLEnabled", "TTLDisabled"} {
t.Run(stream, func(t *testing.T) {
sc, err := js.PullSubscribe("test", "consumer", nats.BindStream(stream))
require_NoError(t, err)
msgs, err := sc.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_Equal(t, msgs[0].Header.Get(JSMessageTTL), "1s")
time.Sleep(time.Second)
si, err := js.StreamInfo(stream)
require_NoError(t, err)
if stream != "TTLDisabled" {
require_Equal(t, si.State.Msgs, 0)
return
}
require_Equal(t, si.State.Msgs, 1)
ttlDisabledConfig.AllowMsgTTL = true
_, err = jsStreamUpdate(t, nc, ttlDisabledConfig)
require_NoError(t, err)
si, err = js.StreamInfo(stream)
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 0)
})
}
})
}
}
func TestJetStreamSubjectDeleteMarkers(t *testing.T) {
for _, storage := range []StorageType{FileStorage, MemoryStorage} {
t.Run(storage.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Storage: storage,
Subjects: []string{"test"},
MaxAge: time.Second,
AllowMsgTTL: true,
SubjectDeleteMarkerTTL: time.Second,
})
sub, err := js.SubscribeSync("test")
require_NoError(t, err)
for i := 0; i < 3; i++ {
_, err = js.Publish("test", nil)
require_NoError(t, err)
}
for i := 0; i < 3; i++ {
msg, err := sub.NextMsg(time.Second)
require_NoError(t, err)
require_NoError(t, msg.AckSync())
}
msg, err := sub.NextMsg(time.Second * 10)
require_NoError(t, err)
require_Equal(t, msg.Header.Get(JSMarkerReason), "MaxAge")
require_Equal(t, msg.Header.Get(JSMessageTTL), "1s")
})
}
}
func TestJetStreamSubjectDeleteMarkersAfterRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Storage: FileStorage,
Subjects: []string{"test"},
MaxAge: time.Second,
AllowMsgTTL: true,
SubjectDeleteMarkerTTL: time.Second,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Name: "test_consumer",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
for i := 0; i < 3; i++ {
_, err = js.Publish("test", nil)
require_NoError(t, err)
}
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
sub, err := js.PullSubscribe("test", _EMPTY_, nats.Bind("TEST", "test_consumer"))
require_NoError(t, err)
for i := 0; i < 3; i++ {
msgs, err := sub.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_NoError(t, msgs[0].AckSync())
}
msgs, err := sub.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_Equal(t, msgs[0].Header.Get(JSMarkerReason), "MaxAge")
require_Equal(t, msgs[0].Header.Get(JSMessageTTL), "1s")
}
func TestJetStreamSubjectDeleteMarkersTTLRollupWithMaxAge(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Storage: FileStorage,
Subjects: []string{"test"},
MaxAge: time.Second,
AllowMsgTTL: true,
AllowRollup: true,
SubjectDeleteMarkerTTL: time.Second,
})
sub, err := js.SubscribeSync("test")
require_NoError(t, err)
for i := 0; i < 3; i++ {
_, err = js.Publish("test", nil)
require_NoError(t, err)
}
for i := 0; i < 3; i++ {
msg, err := sub.NextMsg(time.Second)
require_NoError(t, err)
require_NoError(t, msg.AckSync())
}
rh := nats.Header{}
rh.Set(JSMessageTTL, "2s") // MaxAge will get here first.
rh.Set(JSMsgRollup, JSMsgRollupSubject)
_, err = js.PublishMsg(&nats.Msg{
Subject: "test",
Header: rh,
})
require_NoError(t, err)
// Expect to only have the rollup message here.
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 1)
require_Equal(t, si.State.FirstSeq, 4)
msg, err := sub.NextMsg(time.Second * 10)
require_NoError(t, err)
require_Equal(t, msg.Header.Get(JSMsgRollup), JSMsgRollupSubject)
require_Equal(t, msg.Header.Get(JSMessageTTL), "2s")
meta, err := msg.Metadata()
require_NoError(t, err)
require_NoError(t, msg.AckSync())
msg, err = sub.NextMsg(time.Second * 10)
require_NoError(t, err)
require_LessThan(t, time.Second, time.Since(meta.Timestamp))
require_Equal(t, msg.Header.Get(JSMarkerReason), "MaxAge")
require_Equal(t, msg.Header.Get(JSMessageTTL), "1s")
}
func TestJetStreamSubjectDeleteMarkersTTLRollupWithoutMaxAge(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Storage: FileStorage,
Subjects: []string{"test"},
AllowMsgTTL: true,
AllowRollup: true,
SubjectDeleteMarkerTTL: time.Second,
})
require_NoError(t, err)
sub, err := js.SubscribeSync("test")
require_NoError(t, err)
for i := 0; i < 3; i++ {
_, err = js.Publish("test", nil)
require_NoError(t, err)
}
for i := 0; i < 3; i++ {
msg, err := sub.NextMsg(time.Second)
require_NoError(t, err)
require_NoError(t, msg.AckSync())
}
rh := nats.Header{}
rh.Set(JSMessageTTL, "1s")
rh.Set(JSMsgRollup, JSMsgRollupSubject)
_, err = js.PublishMsg(&nats.Msg{
Subject: "test",
Header: rh,
})
require_NoError(t, err)
// Expect to only have the rollup message here.
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 1)
require_Equal(t, si.State.FirstSeq, 4)
msg, err := sub.NextMsg(time.Second * 10)
require_NoError(t, err)
require_Equal(t, msg.Header.Get(JSMsgRollup), JSMsgRollupSubject)
require_Equal(t, msg.Header.Get(JSMessageTTL), "1s")
require_NoError(t, msg.AckSync())
// Wait for the rollup message to hit the TTL.
time.Sleep(2500 * time.Millisecond)
// Now it should be gone, and it will have been replaced with a
// subject delete marker (which is also gone by now).
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 0)
require_Equal(t, si.State.FirstSeq, 6)
}
func TestJetStreamSubjectDeleteMarkersWithMirror(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, _ := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "Origin",
Storage: FileStorage,
Subjects: []string{"test"},
MaxAge: time.Second,
})
require_NoError(t, err)
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "Mirror",
Storage: FileStorage,
AllowMsgTTL: true,
SubjectDeleteMarkerTTL: time.Second,
Mirror: &StreamSource{
Name: "Origin",
},
})
require_Error(t, err)
}
// https://github.com/nats-io/nats-server/issues/6538
func TestJetStreamInterestMaxDeliveryReached(t *testing.T) {
maxWait := 250 * time.Millisecond
for _, useNak := range []bool{true, false} {
for _, test := range []struct {
title string
action func(s *Server, sub *nats.Subscription)
}{
{
title: "fetch",
action: func(s *Server, sub *nats.Subscription) {
time.Sleep(time.Second)
// max deliver 1 so this will fail
_, err := sub.Fetch(1, nats.MaxWait(maxWait))
require_Error(t, err)
},
},
{
title: "expire pending",
action: func(s *Server, sub *nats.Subscription) {
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()
o.forceExpirePending()
o.mu.Unlock()
},
},
} {
title := fmt.Sprintf("nak/%s", test.title)
if !useNak {
title = fmt.Sprintf("no-%s", title)
}
t.Run(title, func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Storage: nats.FileStorage,
Subjects: []string{"test"},
Replicas: 1,
Retention: nats.InterestPolicy,
})
require_NoError(t, err)
sub, err := js.PullSubscribe("test", "consumer", nats.AckWait(time.Second), nats.MaxDeliver(1))
require_NoError(t, err)
_, err = nc.Request("test", []byte("hello"), maxWait)
require_NoError(t, err)
nfo, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, nfo.State.Msgs, uint64(1))
msg, err := sub.Fetch(1, nats.MaxWait(maxWait))
require_NoError(t, err)
require_Len(t, 1, len(msg))
if useNak {
require_NoError(t, msg[0].Nak())
}
cnfo, err := js.ConsumerInfo("TEST", "consumer")
require_NoError(t, err)
require_Equal(t, cnfo.NumAckPending, 1)
test.action(s, sub)
// max deliver 1 so this will fail
_, err = sub.Fetch(1, nats.MaxWait(maxWait))
require_Error(t, err)
cnfo, err = js.ConsumerInfo("TEST", "consumer")
require_NoError(t, err)
require_Equal(t, cnfo.NumAckPending, 0)
nfo, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, nfo.State.Msgs, uint64(1))
sub2, err := js.PullSubscribe("test", "consumer2")
require_NoError(t, err)
msg, err = sub2.Fetch(1)
require_NoError(t, err)
require_Len(t, 1, len(msg))
require_NoError(t, msg[0].AckSync())
nfo, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, nfo.State.Msgs, uint64(1))
})
}
}
}
// https://github.com/nats-io/nats-server/issues/7817
func TestJetStreamWQMaxDeliveryReached(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)
for range 3 {
_, err = js.Publish("foo", nil)
require_NoError(t, err)
}
sub, err := js.PullSubscribe(_EMPTY_, "CONSUMER",
nats.BindStream("TEST"),
nats.MaxDeliver(2),
nats.AckExplicit(),
nats.AckWait(200*time.Millisecond),
)
require_NoError(t, err)
defer sub.Drain()
msgs, err := sub.Fetch(3, nats.MaxWait(time.Second))
require_NoError(t, err)
require_Len(t, len(msgs), 3)
msgs, err = sub.Fetch(3, nats.MaxWait(time.Second))
require_NoError(t, err)
require_Len(t, len(msgs), 3)
_, err = sub.Fetch(1, nats.MaxWait(500*time.Millisecond))
require_Error(t, err, nats.ErrTimeout)
_, err = js.Publish("foo", nil)
require_NoError(t, err)
msgs, err = sub.Fetch(1, nats.MaxWait(500*time.Millisecond))
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_NoError(t, msgs[0].AckSync())
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)
o.mu.RLock()
pending, rdc, adflr, asflr := len(o.pending), len(o.rdc), o.adflr, o.asflr
o.mu.RUnlock()
require_Equal(t, pending, 0)
require_Equal(t, rdc, 3)
require_Equal(t, adflr, 7)
require_Equal(t, asflr, 4)
mset.checkInterestState()
time.Sleep(200 * time.Millisecond)
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
return checkState(t, c, globalAccountName, "TEST")
})
sm, err := mset.store.LoadMsg(1, nil)
require_NoError(t, err)
require_Equal(t, sm.subj, "foo")
}
// https://github.com/nats-io/nats-server/issues/6874
func TestJetStreamMaxDeliveryRedeliveredReporting(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Storage: nats.FileStorage,
Subjects: []string{"foo"},
Replicas: 1,
Retention: nats.LimitsPolicy,
})
require_NoError(t, err)
_, err = js.Publish("foo", nil)
require_NoError(t, err)
maxWait := 250 * time.Millisecond
cfg := &nats.ConsumerConfig{
Durable: "CONSUMER",
AckPolicy: nats.AckExplicitPolicy,
AckWait: maxWait,
MaxDeliver: 1,
}
_, err = js.AddConsumer("TEST", cfg)
require_NoError(t, err)
sub, err := js.PullSubscribe(_EMPTY_, "CONSUMER", nats.BindStream("TEST"))
require_NoError(t, err)
nfo, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, nfo.State.Msgs, uint64(1))
msgs, err := sub.Fetch(1, nats.MaxWait(maxWait))
require_NoError(t, err)
require_Len(t, 1, len(msgs))
cnfo, err := js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_Equal(t, cnfo.NumAckPending, 1)
require_Equal(t, cnfo.NumRedelivered, 0)
time.Sleep(2 * maxWait)
// Max deliver 1 so this will fail.
_, err = sub.Fetch(1, nats.MaxWait(maxWait))
require_Error(t, err)
// Redelivered should remain 0, as it doesn't get redelivered with MaxDeliver 1.
cnfo, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_Equal(t, cnfo.NumAckPending, 0)
require_Equal(t, cnfo.NumRedelivered, 0)
// With a higher MaxDeliver we should report it.
cfg.MaxDeliver = 2
_, err = js.UpdateConsumer("TEST", cfg)
require_NoError(t, err)
cnfo, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_Equal(t, cnfo.NumAckPending, 0)
require_Equal(t, cnfo.NumRedelivered, 1)
// Unset should also report.
cfg.MaxDeliver = -1
_, err = js.UpdateConsumer("TEST", cfg)
require_NoError(t, err)
cnfo, err = js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_Equal(t, cnfo.NumAckPending, 0)
require_Equal(t, cnfo.NumRedelivered, 1)
}
func TestJetStreamRecoversStreamFirstSeqWhenNotEmpty(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Storage: nats.FileStorage,
Subjects: []string{"test"},
Retention: nats.InterestPolicy,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{
Name: "CONSUMER",
FilterSubject: "test",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
for i := 0; i < 1000; i++ {
_, err = js.Publish("test", nil)
require_NoError(t, err)
}
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 1000)
require_Equal(t, si.State.FirstSeq, 1)
require_Equal(t, si.State.LastSeq, 1000)
ps, err := js.PullSubscribe("test", "", nats.Bind("TEST", "CONSUMER"))
require_NoError(t, err)
for i := 0; i < 500; i++ {
msgs, err := ps.Fetch(1)
require_NoError(t, err)
require_Len(t, len(msgs), 1)
require_NoError(t, msgs[0].AckSync())
}
ci, err := js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_Equal(t, ci.AckFloor.Stream, 500)
s.shutdownJetStream()
path := filepath.Join(s.opts.StoreDir, "jetstream", globalAccountName, "streams", "TEST", msgDir)
dir, err := os.ReadDir(path)
require_NoError(t, err)
// Mangle the last message in the block so that it fails the checksum comparison
// with index.db, which causes us to throw the prior state error and rebuild.
// Once this is done we should still be able to figure out what the previous first
// and last sequence were, even without messages.
for _, f := range dir {
if !strings.HasSuffix(f.Name(), ".blk") {
continue
}
st, err := f.Info()
require_NoError(t, err)
require_NoError(t, os.Truncate(filepath.Join(path, f.Name()), st.Size()-1))
}
require_NoError(t, s.restartJetStream())
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 500)
require_Equal(t, si.State.FirstSeq, 501)
require_Equal(t, si.State.LastSeq, 1000)
}
func TestJetStreamRecoversStreamFirstSeqWhenEmpty(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Storage: nats.FileStorage,
Subjects: []string{"test"},
Retention: nats.InterestPolicy,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "CONSUMER"})
require_NoError(t, err)
for i := 0; i < 1000; i++ {
_, err = js.Publish("test", nil)
require_NoError(t, err)
}
require_NoError(t, js.PurgeStream("TEST"))
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 0)
require_Equal(t, si.State.FirstSeq, 1001)
require_Equal(t, si.State.LastSeq, 1000)
ci, err := js.ConsumerInfo("TEST", "CONSUMER")
require_NoError(t, err)
require_Equal(t, ci.AckFloor.Stream, 1000)
s.shutdownJetStream()
path := filepath.Join(s.opts.StoreDir, "jetstream", globalAccountName, "streams", "TEST", msgDir)
dir, err := os.ReadDir(path)
require_NoError(t, err)
// Mangle the last message in the block so that it fails the checksum comparison
// with index.db, which causes us to throw the prior state error and rebuild.
// Once this is done we should still be able to figure out what the previous first
// and last sequence were, even without messages.
for _, f := range dir {
if !strings.HasSuffix(f.Name(), ".blk") {
continue
}
st, err := f.Info()
require_NoError(t, err)
require_NoError(t, os.Truncate(filepath.Join(path, f.Name()), st.Size()-1))
}
s.restartJetStream()
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 0)
require_Equal(t, si.State.FirstSeq, 1001)
require_Equal(t, si.State.LastSeq, 1000)
}
func TestJetStreamUpgradeStreamVersioning(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
acc, err := s.lookupAccount(globalAccountName)
require_NoError(t, err)
// Create stream config.
cfg, apiErr := s.checkStreamCfg(&StreamConfig{Name: "TEST", Subjects: []string{"foo"}}, acc, false)
require_True(t, apiErr == nil)
// Create stream.
mset, err := acc.addStream(&cfg)
require_NoError(t, err)
require_True(t, mset.cfg.Metadata == 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 TestJetStreamUpgradeConsumerVersioning(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Retention: nats.LimitsPolicy,
Replicas: 1,
})
require_NoError(t, err)
acc, err := s.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
// Create consumer config.
cfg := &ConsumerConfig{Durable: "CONSUMER", Name: "CONSUMER"}
selectedLimits, _, _, apiErr := acc.selectLimits(cfg.replicas(&mset.cfg))
if apiErr != nil {
require_NoError(t, apiErr)
}
srvLim := &s.getOpts().JetStreamLimits
apiErr = setConsumerConfigDefaults(cfg, &mset.cfg, srvLim, selectedLimits, false)
if apiErr != nil {
require_NoError(t, apiErr)
}
// Create consumer.
_, err = mset.addConsumer(cfg)
require_NoError(t, err)
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 TestJetStreamMirrorCrossAccountWithFilteredSubjectAndSubjectTransform(t *testing.T) {
conf := createConfFile(t, fmt.Appendf(nil, `
listen: "127.0.0.1:-1"
jetstream: {store_dir: %q}
accounts: {
PUBLIC_ACCOUNT: {
jetstream: enabled
exports: [
{ service: "$JS.API.CONSUMER.CREATE.S.*.public.a", response_type: stream, accounts: ["INTERNAL_ACCOUNT"] },
{ service: "$JS.API.CONSUMER.CREATE.S.*.public.b", response_type: stream, accounts: ["INTERNAL_ACCOUNT"] },
{ service: "$JS.API.CONSUMER.CREATE.S.*.public.c", response_type: stream, accounts: ["INTERNAL_ACCOUNT"] },
{ service: "$JS.FC.>" },
{ stream: "shared.public.>", accounts: ["INTERNAL_ACCOUNT"] }
]
users: [ {user: "public", password: "pwd"} ]
},
INTERNAL_ACCOUNT: {
jetstream: enabled
imports: [
{ service: { account: "PUBLIC_ACCOUNT", subject: "$JS.API.CONSUMER.CREATE.S.*.public.a" }, to: "JS.PUBLIC_ACCOUNT.CONSUMER.CREATE.S.*.public.a" },
{ service: { account: "PUBLIC_ACCOUNT", subject: "$JS.API.CONSUMER.CREATE.S.*.public.b" }, to: "JS.PUBLIC_ACCOUNT.CONSUMER.CREATE.S.*.public.b" },
{ service: { account: "PUBLIC_ACCOUNT", subject: "$JS.API.CONSUMER.CREATE.S.*.public.c" }, to: "JS.PUBLIC_ACCOUNT.CONSUMER.CREATE.S.*.public.c" },
{ service: { account: "PUBLIC_ACCOUNT", subject: "$JS.FC.>" }, to: "$JS.FC.>" },
{ stream: { account: "PUBLIC_ACCOUNT", subject: "shared.public.>" }, to: "shared.public.>" }
]
users: [ {user: "internal", password: "pwd"} ]
}
}
`, t.TempDir()))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
pc, pjs := jsClientConnect(t, s, nats.UserInfo("public", "pwd"))
defer pc.Close()
_, err := pjs.AddStream(&nats.StreamConfig{
Name: "S",
Subjects: []string{"public.>"},
})
require_NoError(t, err)
ic, ijs := jsClientConnect(t, s, nats.UserInfo("internal", "pwd"))
defer ic.Close()
addStream := func(name, fs string, transform *nats.SubjectTransformConfig) {
t.Helper()
sc := &nats.StreamConfig{
Name: name,
Mirror: &nats.StreamSource{
Name: "S",
External: &nats.ExternalStream{
APIPrefix: "JS.PUBLIC_ACCOUNT",
DeliverPrefix: "shared.public",
},
},
}
if fs != _EMPTY_ {
sc.Mirror.FilterSubject = fs
} else {
sc.Mirror.SubjectTransforms = append(sc.Mirror.SubjectTransforms, *transform)
}
_, err = ijs.AddStream(sc)
require_NoError(t, err)
}
checkMirror := func(name string, expected int) {
t.Helper()
checkFor(t, time.Second, 15*time.Millisecond, func() error {
si, err := ijs.StreamInfo(name)
if err != nil {
return err
}
if n := int(si.State.Msgs); n != expected {
return fmt.Errorf("Expected %v mirrored message, got %v", expected, n)
}
return nil
})
}
// Just a subject filter
addStream("M1", "public.a", nil)
natsPub(t, pc, "public.a", []byte("hello"))
checkMirror("M1", 1)
// Now try with equivalent subject transform (no destination).
addStream("M2", _EMPTY_, &nats.SubjectTransformConfig{Source: "public.b"})
natsPub(t, pc, "public.b", []byte("hello"))
checkMirror("M2", 1)
// And now with a transform destination.
addStream("M3", _EMPTY_, &nats.SubjectTransformConfig{Source: "public.c", Destination: "public.d"})
natsPub(t, pc, "public.c", []byte("hello"))
checkMirror("M3", 1)
checkMsg := func(stream, subj string, seq uint64) {
t.Helper()
msg, err := ijs.GetMsg(stream, seq)
require_NoError(t, err)
require_Equal(t, msg.Subject, subj)
}
checkMsg("M1", "public.a", 1)
checkMsg("M2", "public.b", 2)
checkMsg("M3", "public.d", 3)
ic.Close()
pc.Close()
s.Shutdown()
s, _ = RunServerWithConfig(conf)
defer s.Shutdown()
ic, ijs = jsClientConnect(t, s, nats.UserInfo("internal", "pwd"))
defer ic.Close()
checkMirror("M1", 1)
checkMirror("M2", 1)
checkMirror("M3", 1)
checkMsg("M1", "public.a", 1)
checkMsg("M2", "public.b", 2)
checkMsg("M3", "public.d", 3)
pc, _ = jsClientConnect(t, s, nats.UserInfo("public", "pwd"))
defer pc.Close()
natsPub(t, pc, "public.a", []byte("hello"))
natsPub(t, pc, "public.b", []byte("hello"))
natsPub(t, pc, "public.c", []byte("hello"))
natsFlush(t, pc)
checkMirror("M1", 2)
checkMirror("M2", 2)
checkMirror("M3", 2)
checkMsg("M1", "public.a", 1)
checkMsg("M2", "public.b", 2)
checkMsg("M3", "public.d", 3)
checkMsg("M1", "public.a", 4)
checkMsg("M2", "public.b", 5)
checkMsg("M3", "public.d", 6)
}
func TestJetStreamFileStoreFirstSeqAfterRestart(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
// Create a stream with a first sequence.
fseq := uint64(10_000)
si, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: nats.FileStorage,
Retention: nats.LimitsPolicy,
FirstSeq: fseq,
})
require_NoError(t, err)
require_Equal(t, si.State.FirstSeq, fseq)
require_Equal(t, si.State.LastSeq, fseq-1)
// Publish one message to have some data in the stream.
pubAck, err := js.Publish("foo", nil)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, fseq)
// Confirm initial stream state.
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 1)
require_Equal(t, si.State.FirstSeq, fseq)
require_Equal(t, si.State.LastSeq, fseq)
// Restart the server.
sd := s.JetStreamConfig().StoreDir
s.Shutdown()
nc.Close()
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
// Stream should come back up with the same state prior to restart.
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 1)
require_Equal(t, si.State.FirstSeq, fseq)
require_Equal(t, si.State.LastSeq, fseq)
}
func TestJetStreamCreateStreamWithSubjectDeleteMarkersOptions(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc := clientConnectToServer(t, s)
defer nc.Close()
cfg := StreamConfig{
Name: "PEDANTIC",
Storage: FileStorage,
Subjects: []string{"pedantic"},
SubjectDeleteMarkerTTL: -time.Millisecond,
}
_, err := addStreamPedanticWithError(t, nc, &StreamConfigRequest{cfg, true})
require_Error(t, err, errors.New("subject delete marker TTL must not be negative"))
cfg.SubjectDeleteMarkerTTL = time.Millisecond
_, err = addStreamPedanticWithError(t, nc, &StreamConfigRequest{cfg, true})
require_Error(t, err, errors.New("subject delete marker TTL must be at least 1 second"))
cfg.SubjectDeleteMarkerTTL = time.Second
_, err = addStreamPedanticWithError(t, nc, &StreamConfigRequest{cfg, true})
require_Error(t, err, errors.New("subject delete marker cannot be set if message TTLs are disabled"))
cfg.AllowMsgTTL = true
_, err = addStreamPedanticWithError(t, nc, &StreamConfigRequest{cfg, true})
require_Error(t, err, errors.New("subject delete marker cannot be set if roll-ups are disabled"))
cfg.AllowRollup = true
cfg.DenyPurge = true
_, err = addStreamPedanticWithError(t, nc, &StreamConfigRequest{cfg, true})
require_Error(t, err, errors.New("roll-ups require the purge permission"))
cfg.DenyPurge = false
_, err = addStreamPedanticWithError(t, nc, &StreamConfigRequest{cfg, true})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Automatically setup pre-requisites for SDM.
cfg = StreamConfig{
Name: "AUTO",
Storage: FileStorage,
Subjects: []string{"auto"},
SubjectDeleteMarkerTTL: time.Second,
}
si, err := addStreamPedanticWithError(t, nc, &StreamConfigRequest{cfg, false})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
require_Equal(t, si.Config.SubjectDeleteMarkerTTL, time.Second)
require_True(t, si.Config.AllowMsgTTL)
require_True(t, si.Config.AllowRollup)
require_False(t, si.Config.DenyPurge)
// Allow updating to use SDM and TTL.
cfg = StreamConfig{
Name: "UPDATE",
Storage: FileStorage,
Subjects: []string{"update"},
}
si, err = addStreamPedanticWithError(t, nc, &StreamConfigRequest{cfg, false})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
require_False(t, si.Config.AllowMsgTTL)
require_False(t, si.Config.AllowRollup)
require_False(t, si.Config.DenyPurge)
cfg.SubjectDeleteMarkerTTL = time.Second
si, err = updateStreamPedanticWithError(t, nc, &StreamConfigRequest{cfg, false})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
require_Equal(t, si.Config.SubjectDeleteMarkerTTL, time.Second)
require_True(t, si.Config.AllowMsgTTL)
require_True(t, si.Config.AllowRollup)
require_False(t, si.Config.DenyPurge)
// Should not be allowed to disable msg TTL.
cfg = si.Config
cfg.SubjectDeleteMarkerTTL = 0
cfg.AllowMsgTTL = false
_, err = updateStreamPedanticWithError(t, nc, &StreamConfigRequest{cfg, true})
require_Error(t, err, errors.New("message TTL status can not be disabled"))
}
func TestJetStreamTHWExpireTasksRace(t *testing.T) {
for _, storageType := range []StorageType{FileStorage, MemoryStorage} {
t.Run(storageType.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Storage: storageType,
Subjects: []string{"foo"},
AllowMsgTTL: true,
})
require_NoError(t, err)
acc, err := s.lookupAccount(globalAccountName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
// Send a bunch of message that need to be expired.
m := nats.NewMsg("foo")
m.Header.Set(JSMessageTTL, "1s")
for i := 0; i < 10_000; i++ {
_, err = js.PublishMsg(m)
require_NoError(t, err)
}
// Manually lock so that expirations can't be done without us unlocking.
if fs, ok := mset.store.(*fileStore); ok {
fs.mu.Lock()
} else if ms, ok := mset.store.(*memStore); ok {
ms.mu.Lock()
}
// Wait for all message TTLs to have expired.
time.Sleep(1500 * time.Millisecond)
// Spawn a number of goroutines that will all want to lock and unlock the store lock.
n := 50
var ready sync.WaitGroup
var wg sync.WaitGroup
ready.Add(n)
wg.Add(n)
for i := 0; i < n; i++ {
go func() {
defer wg.Done()
ready.Done()
if fs, ok := mset.store.(*fileStore); ok {
fs.expireMsgs()
} else if ms, ok := mset.store.(*memStore); ok {
ms.expireMsgs()
}
}()
}
// Wait for all goroutines to be ready.
ready.Wait()
// Manually unlock so that goroutines can run expirations in parallel.
if fs, ok := mset.store.(*fileStore); ok {
fs.mu.Unlock()
} else if ms, ok := mset.store.(*memStore); ok {
ms.mu.Unlock()
}
// Wait for all goroutines to finish.
wg.Wait()
// Run once more to clean up for removed messages.
if fs, ok := mset.store.(*fileStore); ok {
fs.expireMsgs()
} else if ms, ok := mset.store.(*memStore); ok {
ms.expireMsgs()
}
// Count of entries in the THW should be exactly 0, and not underflow.
var hwCount uint64
if fs, ok := mset.store.(*fileStore); ok {
fs.mu.Lock()
hwCount = fs.ttls.Count()
fs.mu.Unlock()
} else if ms, ok := mset.store.(*memStore); ok {
ms.mu.Lock()
hwCount = ms.ttls.Count()
ms.mu.Unlock()
}
require_Equal(t, hwCount, 0)
})
}
}
func TestJetStreamRejectLargePublishes(t *testing.T) {
tdir := t.TempDir()
// The test relies on the MaxPayload being larger than the
// rlBadThresh, otherwise you can't publish a message large
// enough.
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
max_payload: %d
jetstream: {store_dir: %q}
`, rlBadThresh+2048, tdir)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"test"},
})
require_NoError(t, err)
_, err = js.Publish("test", make([]byte, rlBadThresh-1024))
require_NoError(t, err)
_, err = js.Publish("test", make([]byte, rlBadThresh+1024))
require_Error(t, err)
require_Contains(t, err.Error(), ErrMsgTooLarge.Error())
}
func TestJetStreamDirectGetSubjectDeleteMarker(t *testing.T) {
for _, storageType := range []nats.StorageType{nats.FileStorage, nats.MemoryStorage} {
t.Run(storageType.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"test"},
Storage: storageType,
SubjectDeleteMarkerTTL: time.Second,
AllowMsgTTL: true,
AllowDirect: true,
})
require_NoError(t, err)
m := nats.NewMsg("test")
m.Header.Set(JSMessageTTL, "1s")
_, err = js.PublishMsg(m)
require_NoError(t, err)
first, err := js.GetLastMsg("TEST", "test", nats.DirectGet())
require_NoError(t, err)
require_Equal(t, first.Header.Get(JSSequence), "1")
time.Sleep(1500 * time.Millisecond)
second, err := js.GetLastMsg("TEST", "test", nats.DirectGet())
require_NoError(t, err)
require_Equal(t, second.Header.Get(JSSequence), "2")
})
}
}
func TestJetStreamPurgeExSeqSimple(t *testing.T) {
for _, storageType := range []nats.StorageType{nats.FileStorage, nats.MemoryStorage} {
t.Run(storageType.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"test"},
Storage: storageType,
})
require_NoError(t, err)
data := make([]byte, 1024)
for i := 0; i < 10_000; i++ {
_, err = js.Publish("test", data)
require_NoError(t, err)
}
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 10_000)
require_NoError(t, js.PurgeStream("TEST", &nats.StreamPurgeRequest{Sequence: 9_000}))
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 1_001)
require_Equal(t, si.State.NumDeleted, 0)
require_Equal(t, si.State.FirstSeq, 9_000)
require_Equal(t, si.State.LastSeq, 10_000)
})
}
}
func TestJetStreamPurgeExSeqInInteriorDeleteGap(t *testing.T) {
for _, storageType := range []nats.StorageType{nats.FileStorage, nats.MemoryStorage} {
t.Run(storageType.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"test.*"},
Storage: storageType,
})
require_NoError(t, err)
data := make([]byte, 1024)
_, err = js.Publish("test.start", data)
require_NoError(t, err)
for i := 0; i < 10_000; i++ {
_, err = js.Publish("test.mid", data)
require_NoError(t, err)
}
_, err = js.Publish("test.end", data)
require_NoError(t, err)
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 10_002)
require_NoError(t, js.PurgeStream("TEST", &nats.StreamPurgeRequest{Subject: "test.mid"}))
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 2)
require_Equal(t, si.State.NumDeleted, 10_000)
require_NoError(t, js.PurgeStream("TEST", &nats.StreamPurgeRequest{Sequence: 9_000}))
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 1)
require_Equal(t, si.State.NumDeleted, 0)
require_Equal(t, si.State.FirstSeq, 10_002)
require_Equal(t, si.State.LastSeq, 10_002)
})
}
}
func TestJetStreamDirectGetUpToTime(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
AllowDirect: true,
Storage: nats.FileStorage,
})
require_NoError(t, err)
for i := range 10 {
sendStreamMsg(t, nc, "foo", fmt.Sprintf("message %d", i+1))
}
sendRequest := func(mreq *JSApiMsgGetRequest) *nats.Subscription {
t.Helper()
req, err := json.Marshal(mreq)
require_NoError(t, err)
reply := nats.NewInbox()
sub, err := nc.SubscribeSync(reply)
require_NoError(t, err)
require_NoError(t, nc.PublishRequest("$JS.API.DIRECT.GET.TEST", reply, req))
return sub
}
checkResponses := func(t *testing.T, upToTime time.Time, expected ...string) {
t.Helper()
sub := sendRequest(&JSApiMsgGetRequest{MultiLastFor: []string{"foo"}, UpToTime: &upToTime})
defer sub.Unsubscribe()
for _, expect := range expected {
msg, err := sub.NextMsg(25 * time.Millisecond)
require_NoError(t, err)
require_Equal(t, msg.Header.Get(JSSubject), "foo")
require_Equal(t, bytesToString(msg.Data), expect)
}
// By this time we're either at the end of our expected and looking
// for an EOB marker (204) or we're not finding anything (404).
msg, err := sub.NextMsg(25 * time.Millisecond)
require_NoError(t, err)
if len(expected) == 0 {
require_Equal(t, msg.Header.Get("Status"), "404")
} else {
require_Equal(t, msg.Header.Get("Status"), "204")
}
}
t.Run("DistantPast", func(t *testing.T) {
checkResponses(t, time.Time{})
})
t.Run("DistantFuture", func(t *testing.T) {
checkResponses(t, time.Unix(0, math.MaxInt64), "message 10")
})
t.Run("BeforeFirstSeq", func(t *testing.T) {
first, err := js.GetMsg("TEST", 1)
require_NoError(t, err)
checkResponses(t, first.Time)
})
t.Run("BeforeFifthSeq", func(t *testing.T) {
fifth, err := js.GetMsg("TEST", 5)
require_NoError(t, err)
checkResponses(t, fifth.Time, "message 4")
})
}
func TestJetStreamDirectGetStartTimeSingleMsg(t *testing.T) {
for _, storage := range []nats.StorageType{nats.FileStorage, nats.MemoryStorage} {
t.Run(storage.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
AllowDirect: true,
Storage: storage,
})
require_NoError(t, err)
sendStreamMsg(t, nc, "foo", "message")
sendRequest := func(mreq *JSApiMsgGetRequest) *nats.Subscription {
t.Helper()
req, err := json.Marshal(mreq)
require_NoError(t, err)
reply := nats.NewInbox()
sub, err := nc.SubscribeSync(reply)
require_NoError(t, err)
require_NoError(t, nc.PublishRequest("$JS.API.DIRECT.GET.TEST", reply, req))
return sub
}
first, err := js.GetMsg("TEST", 1)
require_NoError(t, err)
future := first.Time.Add(10 * time.Second)
sub := sendRequest(&JSApiMsgGetRequest{StartTime: &future, NextFor: "foo", Batch: 1})
defer sub.Unsubscribe()
msg, err := sub.NextMsg(25 * time.Millisecond)
require_NoError(t, err)
require_Equal(t, msg.Header.Get("Status"), "404")
})
}
}
func TestJetStreamStreamRetentionUpdatesConsumers(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
for _, tc := range []struct {
from RetentionPolicy
to RetentionPolicy
}{
{LimitsPolicy, InterestPolicy},
{InterestPolicy, LimitsPolicy},
} {
from, to, name := tc.from, tc.to, fmt.Sprintf("%sTo%s", tc.from, tc.to)
t.Run(name, func(t *testing.T) {
sc, err := jsStreamCreate(t, nc, &StreamConfig{
Name: name,
Subjects: []string{name},
Retention: from,
Storage: FileStorage,
})
require_NoError(t, err)
_, err = js.AddConsumer(name, &nats.ConsumerConfig{
Name: "test_consumer",
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
mset, err := s.globalAccount().lookupStream(name)
require_NoError(t, err)
o := mset.lookupConsumer("test_consumer")
require_NotNil(t, err)
require_Equal(t, o.retention, from)
sc.Retention = to
_, err = jsStreamUpdate(t, nc, sc)
require_NoError(t, err)
require_Equal(t, o.retention, to)
})
}
}
func TestJetStreamMaxMsgsPerSubjectAndDeliverLastPerSubject(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
const subjects = 300
const msgs = subjects * 10
_, err := js.AddStream(&nats.StreamConfig{
Name: "test",
Subjects: []string{"foo.>", "bar.>"},
Retention: nats.LimitsPolicy,
Storage: nats.FileStorage,
MaxMsgsPerSubject: 5,
})
require_NoError(t, err)
// First, publish some messages that match the consumer filter. These
// are the messages that we expect to consume a subset of. The random
// sequences give us interior gaps after MaxMsgsPerSubject has been
// enforced which is needed for this test to work.
for range msgs {
subj := fmt.Sprintf("foo.%d", rand.Intn(subjects))
_, err = js.Publish(subj, nil)
require_NoError(t, err)
}
// Then publish some messages on a different subject. These won't be
// matched by the consumer filter.
for range msgs / 10 {
subj := fmt.Sprintf("bar.%d", rand.Intn(subjects/10))
_, err = js.Publish(subj, nil)
require_NoError(t, err)
}
// Add a deliver last per consumer that matches the first batch of
// published messages only. We expect at this point that the skiplist
// of the consumer will be for foo.> messages only, but the resume
// sequence will be the stream last sequence at the time, i.e. the
// last bar.> message.
_, err = js.AddConsumer("test", &nats.ConsumerConfig{
Name: "test_consumer",
FilterSubjects: []string{"foo.>"},
DeliverPolicy: nats.DeliverLastPerSubjectPolicy,
AckPolicy: nats.AckExplicitPolicy,
})
require_NoError(t, err)
// Take a copy of the skiplist and the resume value so that we can
// make sure we receive all the messages we expect.
mset, err := s.globalAccount().lookupStream("test")
require_NoError(t, err)
o := mset.lookupConsumer("test_consumer")
o.mu.RLock()
pending := make(map[uint64]struct{}, len(o.lss.seqs))
for _, seq := range o.lss.seqs {
pending[seq] = struct{}{}
}
resume := o.lss.resume
o.mu.RUnlock()
// Now fetch the messages from the consumer.
ps, err := js.PullSubscribe(_EMPTY_, _EMPTY_, nats.Bind("test", "test_consumer"))
require_NoError(t, err)
for range subjects {
msgs, err := ps.Fetch(1)
require_NoError(t, err)
for _, msg := range msgs {
meta, err := msg.Metadata()
require_NoError(t, err)
// We must be expecting this sequence and not have seen it already.
// Once we've seen it, take it out of the map.
_, ok := pending[meta.Sequence.Stream]
require_True(t, ok)
delete(pending, meta.Sequence.Stream)
// Then ack.
require_NoError(t, msg.AckSync())
}
}
// We should have received every message that was in the skiplist.
require_Len(t, len(pending), 0)
// When we've run out of last sequences per subject, the consumer
// should now continue from the resume seq, i.e. the last bar.> message.
o.mu.RLock()
sseq := o.sseq
o.mu.RUnlock()
require_Equal(t, sseq, resume+1)
}
func TestJetStreamAllowMsgCounter(t *testing.T) {
test := func(t *testing.T, replicas int) {
var s *Server
var servers []*Server
if replicas == 1 {
s = RunBasicJetStreamServer(t)
servers = []*Server{s}
defer s.Shutdown()
} else {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
servers = c.servers
s = c.randomServer()
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: FileStorage,
AllowMsgCounter: false,
Replicas: replicas,
}
_, err := jsStreamCreate(t, nc, cfg)
require_NoError(t, err)
// A normal publish will succeed, as it's not a counter.
m := nats.NewMsg("foo")
m.Header.Set("Key", "Value")
pa, err := js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pa.Sequence, 1)
// Stream with disabled counters doesn't allow to publish counters.
m = nats.NewMsg("foo")
m.Header.Set("Nats-Incr", "1")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageIncrDisabledError())
// Enabling counters is not allowed.
cfg.AllowMsgCounter = true
_, err = jsStreamUpdate(t, nc, cfg)
require_Error(t, err, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration update can not change message counter setting")))
// Recreate stream with counters enabled.
require_NoError(t, js.DeleteStream("TEST"))
_, err = jsStreamCreate(t, nc, cfg)
require_NoError(t, err)
// Don't allow if missing counter increment.
m = nats.NewMsg("foo")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageIncrMissingError())
m.Header.Set("Key", "Value")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageIncrMissingError())
// Don't allow if increment contains payload.
m.Header.Set("Nats-Incr", "1")
m.Data = []byte("data")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageIncrPayloadError())
// Don't allow if counter increment is invalid.
m = nats.NewMsg("foo")
m.Header.Set("Nats-Incr", "bogus")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageIncrInvalidError())
validateTotal := func(seq uint64, total *big.Int) {
t.Helper()
rsm, err := js.GetLastMsg("TEST", "foo")
require_NoError(t, err)
require_Equal(t, rsm.Sequence, seq)
var val CounterValue
require_NoError(t, json.Unmarshal(rsm.Data, &val))
var res big.Int
res.SetString(val.Value, 10)
require_Equal(t, res.Int64(), total.Int64())
}
increment := func(incr string, seq uint64, total *big.Int) {
t.Helper()
m = nats.NewMsg("foo")
m.Header.Set("Nats-Incr", incr)
msg, err := nc.RequestMsg(m, time.Second)
require_NoError(t, err)
var pubAck PubAck
require_NoError(t, json.Unmarshal(msg.Data, &pubAck))
require_Equal(t, pubAck.Sequence, seq)
require_Equal(t, pubAck.Value, total.String())
validateTotal(seq, total)
}
// Perform and check increments.
increment("1", 1, big.NewInt(1))
increment("2", 2, big.NewInt(3))
// Can also decrement/reset the counter.
increment("-3", 3, big.NewInt(0))
increment("1", 4, big.NewInt(1))
// Reset back to zero.
increment("-1", 5, big.NewInt(0))
// Check de-duplication.
m = nats.NewMsg("foo")
m.Header.Set("Nats-Incr", "1")
m.Header.Set("Nats-Msg-Id", "dedupe")
msg, err := nc.RequestMsg(m, time.Second)
require_NoError(t, err)
var pubAck1 PubAck
require_NoError(t, json.Unmarshal(msg.Data, &pubAck1))
require_False(t, pubAck1.Duplicate)
require_Equal(t, pubAck1.Sequence, 6)
require_Equal(t, pubAck1.Value, "1")
validateTotal(6, big.NewInt(1))
// Re-send should not up counter, but also not return current value.
// When clustered this state can't be guaranteed.
msg, err = nc.RequestMsg(m, time.Second)
require_NoError(t, err)
var pubAck2 PubAck
require_NoError(t, json.Unmarshal(msg.Data, &pubAck2))
require_True(t, pubAck2.Duplicate)
require_Equal(t, pubAck2.Sequence, 6)
require_Equal(t, pubAck2.Value, _EMPTY_)
validateTotal(6, big.NewInt(1))
// Check rejected headers.
for _, header := range []string{JSMsgRollup, JSExpectedLastSeq, JSExpectedLastSubjSeq, JSExpectedLastSubjSeqSubj, JSExpectedLastMsgId} {
m = nats.NewMsg("foo")
m.Header.Set("Nats-Incr", "1")
m.Header.Set(header, "1")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageIncrInvalidError())
}
// Manually break a counter in storage.
for _, cs := range servers {
mset, err := cs.globalAccount().lookupStream("TEST")
require_NoError(t, err)
seq, _, err := mset.Store().StoreMsg("foo", nil, nil, 0)
require_NoError(t, err)
require_Equal(t, seq, 7)
}
// Should now error, because counter is broken.
m = nats.NewMsg("foo")
m.Header.Set("Nats-Incr", "1")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageCounterBrokenError())
}
t.Run("R1", func(t *testing.T) { test(t, 1) })
t.Run("R3", func(t *testing.T) { test(t, 3) })
}
func TestJetStreamAllowMsgCounterMaxPayloadAndSize(t *testing.T) {
test := func(t *testing.T, replicas int) {
var s *Server
if replicas == 1 {
s = RunBasicJetStreamServer(t)
s.optsMu.Lock()
s.opts.MaxPayload = 1024
s.optsMu.Unlock()
defer s.Shutdown()
} else {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
for _, cs := range c.servers {
cs.optsMu.Lock()
cs.opts.MaxPayload = 1024
cs.optsMu.Unlock()
}
s = c.randomServer()
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: FileStorage,
AllowMsgCounter: true,
Replicas: replicas,
}
_, err := jsStreamCreate(t, nc, cfg)
require_NoError(t, err)
validateTotal := func(seq uint64, total *big.Int) {
t.Helper()
rsm, err := js.GetLastMsg("TEST", "foo")
require_NoError(t, err)
require_Equal(t, rsm.Sequence, seq)
var val CounterValue
require_NoError(t, json.Unmarshal(rsm.Data, &val))
var res big.Int
res.SetString(val.Value, 10)
require_Equal(t, res.Int64(), total.Int64())
}
increment := func(incr string, seq uint64, total *big.Int) {
t.Helper()
m := nats.NewMsg("foo")
m.Header.Set("Nats-Incr", incr)
msg, err := nc.RequestMsg(m, time.Second)
require_NoError(t, err)
var pubAck PubAck
require_NoError(t, json.Unmarshal(msg.Data, &pubAck))
require_Equal(t, pubAck.Sequence, seq)
require_Equal(t, pubAck.Value, total.String())
validateTotal(seq, total)
}
// Check payload exceeded, positive bound.
increment("1", 1, big.NewInt(1))
tooLargeIncrement := strings.Repeat("1", 500)
m := nats.NewMsg("foo")
m.Header.Set("Nats-Incr", tooLargeIncrement)
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSStreamMessageExceedsMaximumError())
// Check payload exceeded, negative bound.
increment("-2", 2, big.NewInt(-1))
m.Header.Set("Nats-Incr", fmt.Sprintf("-%s", tooLargeIncrement))
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSStreamMessageExceedsMaximumError())
// Reset back to zero.
increment("1", 3, big.NewInt(0))
// Exactly equals the size of the message for the first message.
cfg.MaxMsgSize = 37
_, err = jsStreamUpdate(t, nc, cfg)
require_NoError(t, err)
increment("1", 4, big.NewInt(1))
// Next increment bumps over MaxMsgSize limit defined above.
m.Header.Set("Nats-Incr", "10")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSStreamMessageExceedsMaximumError())
}
t.Run("R1", func(t *testing.T) { test(t, 1) })
t.Run("R3", func(t *testing.T) { test(t, 3) })
}
func TestJetStreamAllowMsgCounterIncompatibleSettings(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc := clientConnectToServer(t, s)
defer nc.Close()
// DiscardNew not allowed.
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: FileStorage,
AllowMsgCounter: true,
Discard: DiscardNew,
})
require_Error(t, err, NewJSStreamInvalidConfigError(fmt.Errorf("counter stream cannot use discard new")))
// AllowMsgTTL not allowed.
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: FileStorage,
AllowMsgCounter: true,
AllowMsgTTL: true,
})
require_Error(t, err, NewJSStreamInvalidConfigError(fmt.Errorf("counter stream cannot use message TTLs")))
// AllowMsgSchedules not allowed.
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: FileStorage,
AllowMsgCounter: true,
AllowMsgSchedules: true,
})
require_Error(t, err, NewJSStreamInvalidConfigError(fmt.Errorf("counter stream cannot use message schedules")))
// Only limits retention is allowed.
for _, retention := range []RetentionPolicy{InterestPolicy, WorkQueuePolicy} {
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: FileStorage,
AllowMsgCounter: true,
Retention: retention,
})
require_Error(t, err, NewJSStreamInvalidConfigError(fmt.Errorf("counter stream can only use limits retention")))
}
}
func TestJetStreamAllowMsgCounterMirror(t *testing.T) {
test := func(t *testing.T, replicas int) {
var s *Server
if replicas == 1 {
s = RunBasicJetStreamServer(t)
defer s.Shutdown()
} else {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
s = c.randomServer()
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "O",
Subjects: []string{"foo"},
Storage: FileStorage,
AllowMsgCounter: true,
Replicas: replicas,
})
require_NoError(t, err)
// Mirror with counters enabled is rejected.
mirrorCfg := &StreamConfig{
Name: "M",
Mirror: &StreamSource{Name: "O"},
Storage: FileStorage,
AllowMsgCounter: true,
Replicas: replicas,
}
_, err = jsStreamCreate(t, nc, mirrorCfg)
require_Error(t, err, NewJSMirrorWithCountersError())
// Mirror with verbatim copying of counters.
mirrorCfg.AllowMsgCounter = false
_, err = jsStreamCreate(t, nc, mirrorCfg)
require_NoError(t, err)
// A normal publish will succeed, as it's not a counter.
m := nats.NewMsg("foo")
m.Header.Set("Nats-Incr", "1")
pubAck, err := js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
// Mirror should get message verbatim.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
rsm, err := js.GetMsg("M", 1)
if err != nil {
return err
}
if incr := rsm.Header.Get("Nats-Incr"); incr != "1" {
return fmt.Errorf("incorrect increment: %q", incr)
}
var count CounterValue
if err := json.Unmarshal(rsm.Data, &count); err != nil {
return fmt.Errorf("JSON error: %w", err)
} else if count.Value != "1" {
return fmt.Errorf("unexpected value: %s", rsm.Data)
}
return nil
})
}
t.Run("R1", func(t *testing.T) { test(t, 1) })
t.Run("R3", func(t *testing.T) { test(t, 3) })
}
func TestJetStreamAllowMsgCounterSourceAggregates(t *testing.T) {
test := func(t *testing.T, replicas int) {
var s *Server
if replicas == 1 {
s = RunBasicJetStreamServer(t)
defer s.Shutdown()
} else {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
s = c.randomServer()
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "O1",
Subjects: []string{"foo.1"},
Storage: FileStorage,
AllowMsgCounter: true,
Replicas: replicas,
})
require_NoError(t, err)
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "O2",
Subjects: []string{"foo.2"},
Storage: FileStorage,
AllowMsgCounter: true,
Replicas: replicas,
})
require_NoError(t, err)
// Source will only work if counters are enabled on both streams.
sourceCfg := &StreamConfig{
Name: "M",
Sources: []*StreamSource{
{
Name: "O1",
SubjectTransforms: []SubjectTransformConfig{{Source: "foo.*", Destination: "foo"}},
},
{
Name: "O2",
SubjectTransforms: []SubjectTransformConfig{{Source: "foo.*", Destination: "foo"}},
},
},
Storage: FileStorage,
AllowMsgCounter: true,
Replicas: replicas,
}
_, err = jsStreamCreate(t, nc, sourceCfg)
require_NoError(t, err)
m := nats.NewMsg("foo.1")
m.Header.Set("Nats-Incr", "1")
pubAck, err := js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
m = nats.NewMsg("foo.2")
m.Header.Set("Nats-Incr", "2")
pubAck, err = js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
// Source should aggregate.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
rsm, err := js.GetMsg("M", 2)
if err != nil {
return err
}
var count CounterValue
if err := json.Unmarshal(rsm.Data, &count); err != nil {
return fmt.Errorf("JSON error: %w", err)
} else if count.Value != "3" {
return fmt.Errorf("unexpected value: %s", rsm.Data)
}
return nil
})
}
t.Run("R1", func(t *testing.T) { test(t, 1) })
t.Run("R3", func(t *testing.T) { test(t, 3) })
}
func TestJetStreamAllowMsgCounterSourceVerbatim(t *testing.T) {
test := func(t *testing.T, replicas int) {
var s *Server
if replicas == 1 {
s = RunBasicJetStreamServer(t)
defer s.Shutdown()
} else {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
s = c.randomServer()
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "O1",
Subjects: []string{"foo.1"},
Storage: FileStorage,
AllowMsgCounter: true,
Replicas: replicas,
})
require_NoError(t, err)
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "O2",
Subjects: []string{"foo.2"},
Storage: FileStorage,
AllowMsgCounter: true,
Replicas: replicas,
})
require_NoError(t, err)
// Source will only work if counters are enabled on both streams.
sourceCfg := &StreamConfig{
Name: "M",
Sources: []*StreamSource{
{
Name: "O1",
SubjectTransforms: []SubjectTransformConfig{{Source: "foo.*", Destination: "foo"}},
},
{
Name: "O2",
SubjectTransforms: []SubjectTransformConfig{{Source: "foo.*", Destination: "foo"}},
},
},
Storage: FileStorage,
AllowMsgCounter: false,
Replicas: replicas,
}
_, err = jsStreamCreate(t, nc, sourceCfg)
require_NoError(t, err)
m := nats.NewMsg("foo.1")
m.Header.Set("Nats-Incr", "1")
pubAck, err := js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
// Source should store verbatim.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
rsm, err := js.GetMsg("M", 1)
if err != nil {
return err
}
var count CounterValue
if err := json.Unmarshal(rsm.Data, &count); err != nil {
return fmt.Errorf("JSON error: %w", err)
} else if count.Value != "1" {
return fmt.Errorf("unexpected value: %s", rsm.Data)
}
return nil
})
m = nats.NewMsg("foo.2")
m.Header.Set("Nats-Incr", "2")
pubAck, err = js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
// Source should store verbatim.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
rsm, err := js.GetMsg("M", 2)
if err != nil {
return err
}
var count CounterValue
if err := json.Unmarshal(rsm.Data, &count); err != nil {
return fmt.Errorf("JSON error: %w", err)
} else if count.Value != "2" {
return fmt.Errorf("unexpected value: %s", rsm.Data)
}
return nil
})
}
t.Run("R1", func(t *testing.T) { test(t, 1) })
t.Run("R3", func(t *testing.T) { test(t, 3) })
}
func TestJetStreamAllowMsgCounterSourceStartingAboveZero(t *testing.T) {
test := func(t *testing.T, replicas int) {
var s *Server
if replicas == 1 {
s = RunBasicJetStreamServer(t)
defer s.Shutdown()
} else {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
s = c.randomServer()
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "O1",
Subjects: []string{"foo.1"},
Storage: FileStorage,
AllowMsgCounter: true,
Replicas: replicas,
MaxMsgsPer: 1,
})
require_NoError(t, err)
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "O2",
Subjects: []string{"foo.2"},
Storage: FileStorage,
AllowMsgCounter: true,
Replicas: replicas,
MaxMsgsPer: 1,
})
require_NoError(t, err)
for i := range uint64(5) {
m := nats.NewMsg("foo.1")
m.Header.Set("Nats-Incr", "1")
pubAck, err := js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, i+1)
m = nats.NewMsg("foo.2")
m.Header.Set("Nats-Incr", "2")
pubAck, err = js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, i+1)
}
// Source will only work if counters are enabled on both streams.
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "M",
Subjects: []string{"foo"},
Storage: FileStorage,
AllowMsgCounter: true,
Replicas: replicas,
})
require_NoError(t, err)
// Now make an addition locally on this stream too.
m := nats.NewMsg("foo")
m.Header.Set("Nats-Incr", "1")
pubAck, err := js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
// Source will only work if counters are enabled on both streams.
_, err = jsStreamUpdate(t, nc, &StreamConfig{
Name: "M",
Subjects: []string{"foo"},
Sources: []*StreamSource{
{
Name: "O1",
SubjectTransforms: []SubjectTransformConfig{{Source: "foo.*", Destination: "foo"}},
},
{
Name: "O2",
SubjectTransforms: []SubjectTransformConfig{{Source: "foo.*", Destination: "foo"}},
},
},
Storage: FileStorage,
AllowMsgCounter: true,
Replicas: replicas,
})
require_NoError(t, err)
// Source should aggregate.
var first, second, third, fourth *nats.RawStreamMsg
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
first, err = js.GetMsg("M", 1)
if err != nil {
return err
}
second, err = js.GetMsg("M", 2)
if err != nil {
return err
}
third, err = js.GetMsg("M", 3)
return err
})
// Now make an addition locally on this stream too.
m = nats.NewMsg("foo")
m.Header.Set("Nats-Incr", "1")
pubAck, err = js.PublishMsg(m)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 4)
// Fetch it back out of the stream.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
fourth, err = js.GetMsg("M", 4)
return err
})
// There are no local additions to this counter, but the total
// comprises changes from the sources.
var count CounterValue
require_NoError(t, json.Unmarshal(fourth.Data, &count))
require_Equal(t, count.Value, "17")
// The most recent message should contain information about both
// sources, so let's check.
var sources CounterSources
require_NoError(t, json.Unmarshal([]byte(fourth.Header.Get(JSMessageCounterSources)), &sources))
require_NotNil(t, sources["O1"])
require_NotNil(t, sources["O2"])
require_Equal(t, sources["O1"]["foo.1"], "5")
require_Equal(t, sources["O2"]["foo.2"], "10")
// Since this is the first time we've seen a message from these
// sources, the Nats-Incr header should have been updated to reflect
// the correct delta.
for _, rsm := range []*nats.RawStreamMsg{first, second, third, fourth} {
require_Equal(t, rsm.Subject, "foo") // Subject transform'd
switch rsm {
case first:
require_Equal(t, rsm.Header.Get(JSMessageIncr), "1")
case second, third: // We can't know which order they got sourced in
incr := rsm.Header.Get(JSMessageIncr)
require_True(t, incr == "5" || incr == "10")
case fourth:
require_Equal(t, rsm.Header.Get(JSMessageIncr), "1")
}
}
}
t.Run("R1", func(t *testing.T) { test(t, 1) })
t.Run("R3", func(t *testing.T) { test(t, 3) })
}
func TestJetStreamGetNoHeaders(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: nats.FileStorage,
AllowDirect: true,
})
require_NoError(t, err)
msg := &nats.Msg{
Subject: "foo",
Header: nats.Header{
"test": []string{"something"},
},
Data: []byte{1, 2, 3, 4, 5},
}
_, err = js.PublishMsg(msg)
require_NoError(t, err)
t.Run("MsgGet", func(t *testing.T) {
msgGet := func(noHeaders bool) (payload []byte, hdrs nats.Header) {
getSubj := fmt.Sprintf(JSApiMsgGetT, "TEST")
req := fmt.Appendf(nil, `{"seq":1,"no_hdr":%v}`, noHeaders)
resp, err := nc.Request(getSubj, req, time.Second)
require_NoError(t, err)
var get JSApiMsgGetResponse
require_NoError(t, json.Unmarshal(resp.Data, &get))
payload = get.Message.Data
if len(get.Message.Header) > 0 {
hdrs, err = nats.DecodeHeadersMsg(get.Message.Header)
require_NoError(t, err)
}
return
}
payload, headers := msgGet(false)
require_True(t, bytes.Equal(payload, msg.Data))
require_Equal(t, headers.Get("test"), "something")
payload, headers = msgGet(true)
require_True(t, bytes.Equal(payload, msg.Data))
require_Equal(t, headers.Get("test"), _EMPTY_)
})
t.Run("DirectGet", func(t *testing.T) {
directGet := func(noHeaders bool) (payload []byte, hdrs nats.Header) {
getSubj := fmt.Sprintf(JSDirectMsgGetT, "TEST")
req := fmt.Appendf(nil, `{"seq":1,"no_hdr":%v}`, noHeaders)
resp, err := nc.Request(getSubj, req, time.Second)
require_NoError(t, err)
return resp.Data, resp.Header
}
payload, headers := directGet(false)
require_True(t, bytes.Equal(payload, msg.Data))
require_Equal(t, headers.Get("test"), "something")
payload, headers = directGet(true)
require_True(t, bytes.Equal(payload, msg.Data))
require_Equal(t, headers.Get("test"), _EMPTY_)
require_Equal(t, headers.Get("Nats-Stream"), _EMPTY_)
require_Equal(t, headers.Get("Nats-Sequence"), _EMPTY_)
require_Equal(t, headers.Get("Nats-Time-Stamp"), _EMPTY_)
})
t.Run("DirectGetLastFor", func(t *testing.T) {
directGet := func(noHeaders bool) (payload []byte, hdrs nats.Header) {
getSubj := fmt.Sprintf(JSDirectGetLastBySubjectT, "TEST", "foo")
req := fmt.Appendf(nil, `{"no_hdr":%v}`, noHeaders)
resp, err := nc.Request(getSubj, req, time.Second)
require_NoError(t, err)
return resp.Data, resp.Header
}
payload, headers := directGet(false)
require_True(t, bytes.Equal(payload, msg.Data))
require_Equal(t, headers.Get("test"), "something")
payload, headers = directGet(true)
require_True(t, bytes.Equal(payload, msg.Data))
require_Equal(t, headers.Get("test"), _EMPTY_)
require_Equal(t, headers.Get("Nats-Stream"), _EMPTY_)
require_Equal(t, headers.Get("Nats-Sequence"), _EMPTY_)
require_Equal(t, headers.Get("Nats-Time-Stamp"), _EMPTY_)
})
}
func TestJetStreamKVNoSubjectDeleteMarkerOnPurgeMarker(t *testing.T) {
for _, storage := range []jetstream.StorageType{jetstream.FileStorage, jetstream.MemoryStorage} {
t.Run(storage.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnectNewAPI(t, s)
defer nc.Close()
ctx := context.Background()
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{
Bucket: "bucket",
History: 1,
Storage: storage,
TTL: 2 * time.Second,
LimitMarkerTTL: time.Minute,
})
require_NoError(t, err)
stream, err := js.Stream(ctx, "KV_bucket")
require_NoError(t, err)
// Purge such that the bucket TTL expires this message.
require_NoError(t, kv.Purge(ctx, "key"))
rsm, err := stream.GetMsg(ctx, 1)
require_NoError(t, err)
require_Equal(t, rsm.Header.Get("KV-Operation"), "PURGE")
// The bucket TTL should have removed the message by now.
time.Sleep(2500 * time.Millisecond)
// Confirm the purge marker is gone.
_, err = stream.GetMsg(ctx, 1)
require_Error(t, err, jetstream.ErrMsgNotFound)
require_Equal(t, rsm.Header.Get("KV-Operation"), "PURGE")
// Confirm we don't get a redundant subject delete marker.
_, err = stream.GetMsg(ctx, 2)
require_Error(t, err, jetstream.ErrMsgNotFound)
// Purge with a TTL so it expires this message.
require_NoError(t, kv.Purge(ctx, "key", jetstream.PurgeTTL(time.Second)))
rsm, err = stream.GetMsg(ctx, 2)
require_NoError(t, err)
require_Equal(t, rsm.Header.Get("KV-Operation"), "PURGE")
// The purge TTL should have removed the message by now.
time.Sleep(1500 * time.Millisecond)
// Confirm the purge marker is gone.
_, err = stream.GetMsg(ctx, 2)
require_Error(t, err, jetstream.ErrMsgNotFound)
// Confirm we don't get a redundant subject delete marker.
_, err = stream.GetMsg(ctx, 3)
require_Error(t, err, jetstream.ErrMsgNotFound)
})
}
}
func TestJetStreamInvalidConfigValues(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
acc := s.globalAccount()
_, err := s.checkStreamCfg(&StreamConfig{Name: "TEST", Retention: -1}, acc, false)
require_True(t, err != nil)
require_Error(t, err, NewJSStreamInvalidConfigError(errors.New("invalid retention")))
_, err = s.checkStreamCfg(&StreamConfig{Name: "TEST", Discard: -1}, acc, false)
require_True(t, err != nil)
require_Error(t, err, NewJSStreamInvalidConfigError(errors.New("invalid discard policy")))
_, err = s.checkStreamCfg(&StreamConfig{Name: "TEST", Storage: -1}, acc, false)
require_True(t, err != nil)
require_Error(t, err, NewJSStreamInvalidConfigError(errors.New("invalid storage type")))
_, err = s.checkStreamCfg(&StreamConfig{Name: "TEST", Compression: 255}, acc, false)
require_True(t, err != nil)
require_Error(t, err, NewJSStreamInvalidConfigError(errors.New("invalid compression")))
_, err = s.checkStreamCfg(&StreamConfig{Name: "TEST", MaxAge: -time.Second}, acc, false)
require_True(t, err != nil)
require_Error(t, err, NewJSStreamInvalidConfigError(errors.New("max age can not be negative")))
scfg := StreamConfig{Name: "TEST"}
streamTests := []struct {
name string
setValue func(value int)
getValue func() int
}{
{
name: "max_msgs",
setValue: func(value int) { scfg.MaxMsgs = int64(value) },
getValue: func() int { return int(scfg.MaxMsgs) },
},
{
name: "max_msgs_per_subject",
setValue: func(value int) { scfg.MaxMsgsPer = int64(value) },
getValue: func() int { return int(scfg.MaxMsgsPer) },
},
{
name: "max_bytes",
setValue: func(value int) { scfg.MaxBytes = int64(value) },
getValue: func() int { return int(scfg.MaxBytes) },
},
{
name: "max_msg_size",
setValue: func(value int) { scfg.MaxMsgSize = int32(value) },
getValue: func() int { return int(scfg.MaxMsgSize) },
},
{
name: "max_consumers",
setValue: func(value int) { scfg.MaxConsumers = value },
getValue: func() int { return scfg.MaxConsumers },
},
}
for _, streamTest := range streamTests {
// Pedantic errors if less than -1.
streamTest.setValue(-10)
_, err = s.checkStreamCfg(&scfg, acc, true)
if err == nil {
t.Fatal("Expected error for pedantic mode")
}
require_Error(t, err, NewJSPedanticError(fmt.Errorf("%s must be set to -1", streamTest.name)))
// Pedantic defaults if zero-value.
streamTest.setValue(0)
scfg, err = s.checkStreamCfg(&scfg, acc, true)
if err != nil {
require_NoError(t, err)
}
require_Equal(t, streamTest.getValue(), -1)
// Non-pedantic defaults.
streamTest.setValue(-10)
scfg, err = s.checkStreamCfg(&scfg, acc, false)
if err != nil {
require_NoError(t, err)
}
require_Equal(t, streamTest.getValue(), -1)
}
ccfg := &ConsumerConfig{AckPolicy: -1}
err = checkConsumerCfg(ccfg, &JSLimitOpts{}, &scfg, nil, &JetStreamAccountLimits{}, false)
require_True(t, err != nil)
require_Error(t, err, NewJSConsumerAckPolicyInvalidError())
ccfg = &ConsumerConfig{ReplayPolicy: -1}
err = checkConsumerCfg(ccfg, &JSLimitOpts{}, &scfg, nil, &JetStreamAccountLimits{}, false)
require_True(t, err != nil)
require_Error(t, err, NewJSConsumerReplayPolicyInvalidError())
ccfg = &ConsumerConfig{AckWait: -time.Second}
err = checkConsumerCfg(ccfg, &JSLimitOpts{}, &scfg, nil, &JetStreamAccountLimits{}, false)
require_True(t, err != nil)
require_Error(t, err, NewJSConsumerAckWaitNegativeError())
ccfg = &ConsumerConfig{BackOff: []time.Duration{-time.Second}}
err = checkConsumerCfg(ccfg, &JSLimitOpts{}, &scfg, nil, &JetStreamAccountLimits{}, false)
require_True(t, err != nil)
require_Error(t, err, NewJSConsumerBackOffNegativeError())
ccfg = &ConsumerConfig{AckPolicy: AckExplicit}
consumerTests := []struct {
name string
setValue func(value int)
getValue func() int
defaultValue int
}{
{
name: "max_deliver",
setValue: func(value int) { ccfg.MaxDeliver = value },
getValue: func() int { return ccfg.MaxDeliver },
defaultValue: -1,
},
{
name: "max_waiting",
setValue: func(value int) { ccfg.MaxWaiting = value },
getValue: func() int { return ccfg.MaxWaiting },
defaultValue: JSWaitQueueDefaultMax,
},
{
name: "max_batch",
setValue: func(value int) { ccfg.MaxRequestBatch = value },
getValue: func() int { return ccfg.MaxRequestBatch },
defaultValue: 0,
},
{
name: "max_expires",
setValue: func(value int) { ccfg.MaxRequestExpires = time.Duration(value) },
getValue: func() int { return int(ccfg.MaxRequestExpires) },
defaultValue: 0,
},
{
name: "max_bytes",
setValue: func(value int) { ccfg.MaxRequestMaxBytes = value },
getValue: func() int { return ccfg.MaxRequestMaxBytes },
defaultValue: 0,
},
{
name: "idle_heartbeat",
setValue: func(value int) { ccfg.Heartbeat = time.Duration(value) },
getValue: func() int { return int(ccfg.Heartbeat) },
defaultValue: 0,
},
{
name: "inactive_threshold",
setValue: func(value int) { ccfg.InactiveThreshold = time.Duration(value) },
getValue: func() int { return int(ccfg.InactiveThreshold) },
defaultValue: 0,
},
{
name: "priority_timeout",
setValue: func(value int) { ccfg.PinnedTTL = time.Duration(value) },
getValue: func() int { return int(ccfg.PinnedTTL) },
defaultValue: 0,
},
}
for _, consumerTest := range consumerTests {
// Pedantic errors if less than -1.
consumerTest.setValue(-10)
err = setConsumerConfigDefaults(ccfg, &scfg, &JSLimitOpts{}, &JetStreamAccountLimits{}, true)
if err == nil {
t.Fatal("Expected error for pedantic mode")
}
if consumerTest.defaultValue == -1 {
require_Error(t, err, NewJSPedanticError(fmt.Errorf("%s must be set to -1", consumerTest.name)))
} else {
require_Error(t, err, NewJSPedanticError(fmt.Errorf("%s must not be negative", consumerTest.name)))
}
// Pedantic defaults if zero-value.
consumerTest.setValue(0)
err = setConsumerConfigDefaults(ccfg, &scfg, &JSLimitOpts{}, &JetStreamAccountLimits{}, true)
if err != nil {
require_NoError(t, err)
}
require_Equal(t, consumerTest.getValue(), consumerTest.defaultValue)
// Non-pedantic defaults.
consumerTest.setValue(-10)
err = setConsumerConfigDefaults(ccfg, &scfg, &JSLimitOpts{}, &JetStreamAccountLimits{}, false)
if err != nil {
require_NoError(t, err)
}
require_Equal(t, consumerTest.getValue(), consumerTest.defaultValue)
}
// Pedantic errors if less than -1.
ccfg.MaxAckPending = -10
err = setConsumerConfigDefaults(ccfg, &scfg, &JSLimitOpts{}, &JetStreamAccountLimits{}, true)
if err == nil {
t.Fatal("Expected error for pedantic mode")
}
require_Error(t, err, NewJSPedanticError(errors.New("max_ack_pending must be set to -1")))
// Pedantic defaults if zero-value.
ccfg.MaxAckPending = 0
err = setConsumerConfigDefaults(ccfg, &scfg, &JSLimitOpts{}, &JetStreamAccountLimits{}, true)
if err != nil {
require_NoError(t, err)
}
require_Equal(t, ccfg.MaxAckPending, JsDefaultMaxAckPending)
// Non-pedantic defaults.
ccfg.MaxAckPending = -10
err = setConsumerConfigDefaults(ccfg, &scfg, &JSLimitOpts{}, &JetStreamAccountLimits{}, false)
if err != nil {
require_NoError(t, err)
}
require_Equal(t, ccfg.MaxAckPending, -1)
}
func TestJetStreamInvalidStreamOrConsumerName(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
invalidChars := " \t\r\n\f.*>\\/"
for _, c := range invalidChars {
name := fmt.Sprintf("%c", c)
scfg := &StreamConfig{Name: name}
_, err := s.checkStreamCfg(scfg, nil, false)
require_NotNil(t, err)
require_Contains(t, err.Description, "can not contain")
ccfg := &ConsumerConfig{Name: name}
err = checkConsumerCfg(ccfg, nil, nil, nil, nil, false)
require_NotNil(t, err)
require_Contains(t, err.Description, "can not contain")
ccfg = &ConsumerConfig{Durable: name}
err = checkConsumerCfg(ccfg, nil, nil, nil, nil, false)
require_NotNil(t, err)
require_Contains(t, err.Description, "can not contain")
}
}
func TestJetStreamPromoteMirrorDeletingOrigin(t *testing.T) {
test := func(t *testing.T, replicas int) {
var s *Server
if replicas == 1 {
s = RunBasicJetStreamServer(t)
defer s.Shutdown()
} else {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
s = c.randomServer()
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "O",
Subjects: []string{"foo"},
Storage: FileStorage,
Replicas: replicas,
})
require_NoError(t, err)
mirrorCfg := &StreamConfig{
Name: "M",
Mirror: &StreamSource{Name: "O"},
Storage: FileStorage,
Replicas: replicas,
}
mirrorCfg, err = jsStreamCreate(t, nc, mirrorCfg)
require_NoError(t, err)
pubAck, err := js.Publish("foo", nil)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
// Mirror should get message.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
_, err := js.GetMsg("M", 1)
return err
})
// Trying to promote the mirror with a subject conflict should fail
// because the origin stream is listening on that subject still.
mirrorCfg.Mirror = nil
mirrorCfg.Subjects = []string{"foo"}
_, err = jsStreamUpdate(t, nc, mirrorCfg)
require_Error(t, err)
apiErr, ok := err.(*ApiError)
require_True(t, ok)
require_Equal(t, apiErr.ErrCode, uint16(JSStreamSubjectOverlapErr))
// But if we delete the stream, the subject conflict goes away...
err = js.DeleteStream("O")
require_NoError(t, err)
// ... so now it should work.
mirrorCfg, err = jsStreamUpdate(t, nc, mirrorCfg)
require_NoError(t, err)
require_Len(t, len(mirrorCfg.Subjects), 1)
require_Equal(t, mirrorCfg.Mirror, nil)
// Make sure the mirror state has gone away.
checkFor(t, 5*time.Second, 200*time.Millisecond, func() error {
si, err := js.StreamInfo("M")
if err != nil {
return err
}
if si.Mirror != nil {
return fmt.Errorf("expecting no mirror status, got %+v", si.Mirror)
}
return nil
})
// Now we should be able to publish into the newly promoted mirror.
pubAck, err = js.Publish("foo", nil)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 2)
// ... and confirm that it was received in the mirror stream.
_, err = js.GetMsg("M", 2)
require_NoError(t, err)
}
t.Run("R1", func(t *testing.T) { test(t, 1) })
t.Run("R3", func(t *testing.T) { test(t, 3) })
}
func TestJetStreamPromoteMirrorUpdatingOrigin(t *testing.T) {
test := func(t *testing.T, replicas int) {
var s *Server
if replicas == 1 {
s = RunBasicJetStreamServer(t)
defer s.Shutdown()
} else {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
s = c.randomServer()
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
originCfg, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "O",
Subjects: []string{"foo"},
Storage: FileStorage,
Replicas: replicas,
})
require_NoError(t, err)
mirrorCfg := &StreamConfig{
Name: "M",
Mirror: &StreamSource{Name: "O"},
Storage: FileStorage,
Replicas: replicas,
}
mirrorCfg, err = jsStreamCreate(t, nc, mirrorCfg)
require_NoError(t, err)
pubAck, err := js.Publish("foo", nil)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
// Mirror should get message.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
_, err := js.GetMsg("M", 1)
return err
})
// Trying to promote the mirror with a subject conflict should fail
// because the origin stream is listening on that subject still.
mirrorCfg.Mirror = nil
mirrorCfg.Subjects = []string{"foo"}
_, err = jsStreamUpdate(t, nc, mirrorCfg)
require_Error(t, err)
apiErr, ok := err.(*ApiError)
require_True(t, ok)
require_Equal(t, apiErr.ErrCode, uint16(JSStreamSubjectOverlapErr))
// But if we change the subjects on the origin stream, the subject
// conflict goes away...
originCfg.Subjects = []string{"bar"}
_, err = jsStreamUpdate(t, nc, originCfg)
require_NoError(t, err)
// ... so now it should work.
mirrorCfg, err = jsStreamUpdate(t, nc, mirrorCfg)
require_NoError(t, err)
require_Len(t, len(mirrorCfg.Subjects), 1)
require_Equal(t, mirrorCfg.Mirror, nil)
// Make sure the mirror state has gone away.
checkFor(t, 5*time.Second, 200*time.Millisecond, func() error {
si, err := js.StreamInfo("M")
if err != nil {
return err
}
if si.Mirror != nil {
return fmt.Errorf("expecting no mirror status, got %+v", si.Mirror)
}
return nil
})
// Publishing into the original stream should no longer mirror over
// onto the mirror stream...
pubAck, err = js.Publish("bar", nil)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 2)
// ... therefore a new publish to the mirror stream should also have
// sequence 2 and not 3.
pubAck, err = js.Publish("foo", nil)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 2)
}
t.Run("R1", func(t *testing.T) { test(t, 1) })
t.Run("R3", func(t *testing.T) { test(t, 3) })
}
func TestJetStreamScheduledMirrorOrSource(t *testing.T) {
test := func(t *testing.T, replicas int) {
var s *Server
if replicas == 1 {
s = RunBasicJetStreamServer(t)
defer s.Shutdown()
} else {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
s = c.randomServer()
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "M",
Storage: FileStorage,
Replicas: replicas,
Mirror: &StreamSource{Name: "O"},
AllowMsgSchedules: true,
})
require_Error(t, err, NewJSMirrorWithMsgSchedulesError())
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "S",
Storage: FileStorage,
Replicas: replicas,
Sources: []*StreamSource{{Name: "O"}},
AllowMsgSchedules: true,
})
require_Error(t, err, NewJSSourceWithMsgSchedulesError())
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "O",
Subjects: []string{"O.>"},
Storage: FileStorage,
Replicas: replicas,
AllowMsgSchedules: true,
})
require_NoError(t, err)
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "M",
Storage: FileStorage,
Replicas: replicas,
Mirror: &StreamSource{Name: "O"},
})
require_NoError(t, err)
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "S",
Storage: FileStorage,
Replicas: replicas,
Sources: []*StreamSource{{Name: "O"}},
})
require_NoError(t, err)
m := nats.NewMsg("O.schedule")
pattern := fmt.Sprintf("@at %s", time.Now().Add(time.Second).Format(time.RFC3339Nano))
m.Header.Set("Nats-Schedule", pattern)
m.Header.Set("Nats-Schedule-Target", "O.target")
_, err = js.PublishMsg(m)
require_NoError(t, err)
check := func(streamName string) error {
// Request message 2 first.
// If a rollup was performed due to it, fetching the 1st message next will fail.
sm, err := js.GetMsg(streamName, 2)
if err != nil {
return err
}
if sm.Subject != "O.target" {
return fmt.Errorf("expected subject 'O.target', got '%s'", sm.Subject)
}
sm, err = js.GetMsg(streamName, 1)
if err != nil {
return err
}
if sm.Subject != "O.schedule" {
return fmt.Errorf("expected subject 'O.schedule', got '%s'", sm.Subject)
}
return nil
}
checkFor(t, 5*time.Second, 200*time.Millisecond, func() error {
if err = check("M"); err != nil {
return fmt.Errorf("mirror: %v", err)
}
if err = check("S"); err != nil {
return fmt.Errorf("source: %v", err)
}
return nil
})
}
for _, replicas := range []int{1, 3} {
t.Run(fmt.Sprintf("R%d", replicas), func(t *testing.T) { test(t, replicas) })
}
}
func TestJetStreamRollupMirrorOrSource(t *testing.T) {
test := func(t *testing.T, replicas int) {
var s *Server
if replicas == 1 {
s = RunBasicJetStreamServer(t)
defer s.Shutdown()
} else {
c := createJetStreamClusterExplicit(t, "R3S", 3)
defer c.shutdown()
s = c.randomServer()
}
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "O",
Subjects: []string{"O"},
Storage: FileStorage,
Replicas: replicas,
AllowRollup: true,
})
require_NoError(t, err)
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "M",
Storage: FileStorage,
Replicas: replicas,
Mirror: &StreamSource{Name: "O"},
})
require_NoError(t, err)
_, err = jsStreamCreate(t, nc, &StreamConfig{
Name: "S",
Storage: FileStorage,
Replicas: replicas,
Sources: []*StreamSource{{Name: "O"}},
})
require_NoError(t, err)
m := nats.NewMsg("O")
m.Header.Set("Nats-Rollup", "sub")
_, err = js.PublishMsg(m)
require_NoError(t, err)
checkSequence := func(seq uint64) {
check := func(streamName string) error {
si, err := js.StreamInfo(streamName)
if err != nil {
return err
}
if si.State.Msgs != seq || si.State.FirstSeq != 1 || si.State.LastSeq != seq {
return fmt.Errorf("expected seq %d, got %v", seq, si.State)
}
return nil
}
checkFor(t, 5*time.Second, 200*time.Millisecond, func() error {
if err = check("M"); err != nil {
return fmt.Errorf("mirror: %v", err)
}
if err = check("S"); err != nil {
return fmt.Errorf("source: %v", err)
}
return nil
})
}
checkSequence(1)
_, err = js.PublishMsg(m)
require_NoError(t, err)
checkSequence(2)
}
for _, replicas := range []int{1, 3} {
t.Run(fmt.Sprintf("R%d", replicas), func(t *testing.T) { test(t, replicas) })
}
}
func TestJetStreamOfflineStreamAndConsumerAfterDowngrade(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
port := s.getOpts().Port
sd := s.JetStreamConfig().StoreDir
_, err := s.globalAccount().addStream(&StreamConfig{
Name: "DowngradeStreamTest",
Storage: FileStorage,
Replicas: 1,
Metadata: map[string]string{"_nats.req.level": strconv.Itoa(math.MaxInt)},
})
require_NoError(t, err)
s.Shutdown()
s = RunJetStreamServerOnPort(port, sd)
defer s.Shutdown()
nc := clientConnectToServer(t, s)
defer nc.Close()
offlineReason := fmt.Sprintf("unsupported - required API level: %d, current API level: %d", math.MaxInt, JSApiLevel)
msg, err := nc.Request(fmt.Sprintf(JSApiStreamInfoT, "DowngradeStreamTest"), nil, time.Second)
require_NoError(t, 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], "DowngradeStreamTest")
var sl JSApiStreamListResponse
msg, err = nc.Request(JSApiStreamList, nil, 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], "DowngradeStreamTest")
require_Len(t, len(sl.Offline), 1)
require_Equal(t, sl.Offline["DowngradeStreamTest"], offlineReason)
mset, err := s.globalAccount().lookupStream("DowngradeStreamTest")
require_NoError(t, err)
require_True(t, mset.closed.Load())
require_Equal(t, mset.offlineReason, offlineReason)
require_NoError(t, mset.delete())
s.Shutdown()
s = RunJetStreamServerOnPort(port, sd)
defer s.Shutdown()
_, err = s.globalAccount().addStream(&StreamConfig{
Name: "DowngradeConsumerTest",
Storage: FileStorage,
Replicas: 1,
})
require_NoError(t, err)
mset, err = s.globalAccount().lookupStream("DowngradeConsumerTest")
require_NoError(t, err)
_, err = mset.addConsumer(&ConsumerConfig{
Name: "DowngradeConsumerTest",
Metadata: map[string]string{"_nats.req.level": strconv.Itoa(math.MaxInt)},
})
require_NoError(t, err)
s.Shutdown()
s = RunJetStreamServerOnPort(port, sd)
defer s.Shutdown()
mset, err = s.globalAccount().lookupStream("DowngradeConsumerTest")
require_NoError(t, err)
require_True(t, mset.closed.Load())
require_Equal(t, mset.offlineReason, "stopped - unsupported consumer \"DowngradeConsumerTest\"")
obs := mset.getPublicConsumers()
require_Len(t, len(obs), 1)
require_True(t, obs[0].isClosed())
require_Equal(t, obs[0].offlineReason, offlineReason)
msg, err = nc.Request(fmt.Sprintf(JSApiConsumerInfoT, "DowngradeConsumerTest", "DowngradeConsumerTest"), nil, time.Second)
require_NoError(t, 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), 1)
require_Equal(t, cn.Consumers[0], "DowngradeConsumerTest")
var cl JSApiConsumerListResponse
msg, err = nc.Request(fmt.Sprintf(JSApiConsumerListT, "DowngradeConsumerTest"), nil, 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), 1)
require_Equal(t, cl.Missing[0], "DowngradeConsumerTest")
require_Len(t, len(cl.Offline), 1)
require_Equal(t, cl.Offline["DowngradeConsumerTest"], offlineReason)
}
func TestJetStreamPersistModeAsync(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: MemoryStorage,
PersistMode: AsyncPersistMode,
}
_, err := jsStreamCreate(t, nc, cfg)
require_Error(t, err, NewJSStreamInvalidConfigError(fmt.Errorf("async persist mode is only supported on file storage")))
cfg.Storage = FileStorage
si, err := jsStreamCreate(t, nc, cfg)
require_NoError(t, err)
require_Equal(t, si.PersistMode, AsyncPersistMode)
pubAck, err := js.Publish("foo", nil)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
fs := mset.store.(*fileStore)
fs.mu.RLock()
asyncFlush := fs.fcfg.AsyncFlush
fs.mu.RUnlock()
require_True(t, asyncFlush)
cfg.PersistMode = DefaultPersistMode
_, err = jsStreamUpdate(t, nc, cfg)
require_Error(t, err, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration update can not change persist mode")))
}
func TestJetStreamRemoveTTLOnRemoveMsg(t *testing.T) {
for _, storageType := range []nats.StorageType{nats.FileStorage, nats.MemoryStorage} {
t.Run(storageType.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: storageType,
AllowMsgTTL: true,
})
require_NoError(t, err)
_, err = js.Publish("foo", nil, nats.MsgTTL(time.Hour))
require_NoError(t, err)
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
validateTTLCount := func(count uint64) {
store := mset.Store()
switch storageType {
case nats.FileStorage:
fs := store.(*fileStore)
fs.mu.RLock()
defer fs.mu.RUnlock()
require_Equal(t, fs.ttls.Count(), count)
case nats.MemoryStorage:
ms := store.(*memStore)
ms.mu.RLock()
defer ms.mu.RUnlock()
require_Equal(t, ms.ttls.Count(), count)
}
}
validateTTLCount(1)
require_NoError(t, js.DeleteMsg("TEST", 1))
validateTTLCount(0)
})
}
}
func TestJetStreamMessageTTLNotExpiring(t *testing.T) {
for _, storageType := range []nats.StorageType{nats.FileStorage, nats.MemoryStorage} {
t.Run(storageType.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: storageType,
AllowMsgTTL: true,
})
require_NoError(t, err)
// Triggers the expiry timer once, and needs to be reset to trigger earlier.
_, err = js.Publish("foo", nil, nats.MsgTTL(time.Hour))
require_NoError(t, err)
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
// Storing messages with a TTL would continuously reset the timer.
var wg sync.WaitGroup
wg.Add(1)
defer wg.Wait()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case <-time.After(100 * time.Millisecond):
ttl := time.Hour.Nanoseconds()
store := mset.Store()
store.StoreMsg("foo", nil, nil, ttl)
}
}
}()
// The message should be expired timely.
pubAck, err := js.Publish("foo", nil, nats.MsgTTL(time.Second))
require_NoError(t, err)
checkFor(t, 3*time.Second, 100*time.Millisecond, func() error {
_, err = js.GetMsg("TEST", pubAck.Sequence)
if err == nil {
return fmt.Errorf("message not removed yet")
}
if !errors.Is(err, nats.ErrMsgNotFound) {
return err
}
return nil
})
})
}
}
func TestJetStreamScheduledMessageNotTriggering(t *testing.T) {
for _, storageType := range []StorageType{FileStorage, MemoryStorage} {
t.Run(storageType.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>"},
Storage: storageType,
AllowMsgSchedules: true,
})
require_NoError(t, err)
delay := func(d time.Duration) string {
return fmt.Sprintf("@at %s", time.Now().Add(d).Format(time.RFC3339Nano))
}
// Triggers the schedule timer once, and needs to be reset to trigger earlier.
m := nats.NewMsg("foo.schedule.first")
m.Header.Set("Nats-Schedule", delay(time.Hour))
m.Header.Set("Nats-Schedule-Target", "foo.msg")
_, err = js.PublishMsg(m)
require_NoError(t, err)
// Storing messages with a schedule would continuously reset the timer.
var wg sync.WaitGroup
wg.Add(1)
defer wg.Wait()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
defer wg.Done()
var i int
for {
select {
case <-ctx.Done():
return
case <-time.After(100 * time.Millisecond):
i++
ms := nats.NewMsg(fmt.Sprintf("foo.schedule.%d", i))
ms.Header.Set("Nats-Schedule", delay(time.Hour))
ms.Header.Set("Nats-Schedule-Target", "foo.msg")
js.PublishMsg(ms)
}
}
}()
// The message should be scheduled timely.
m = nats.NewMsg("foo.schedule.validate")
m.Header.Set("Nats-Schedule", delay(time.Second))
m.Header.Set("Nats-Schedule-Target", "foo.msg")
_, err = js.PublishMsg(m)
require_NoError(t, err)
pubAck, err := js.PublishMsg(m)
require_NoError(t, err)
checkFor(t, 3*time.Second, 100*time.Millisecond, func() error {
_, err = js.GetMsg("TEST", pubAck.Sequence)
if err == nil {
return fmt.Errorf("message not removed yet")
}
if !errors.Is(err, nats.ErrMsgNotFound) {
return err
}
return nil
})
})
}
}
func TestJetStreamScheduledMessageIncompatibleSettings(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc := clientConnectToServer(t, s)
defer nc.Close()
// DiscardNew not allowed.
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: FileStorage,
AllowMsgSchedules: true,
Discard: DiscardNew,
})
require_Error(t, err, NewJSStreamInvalidConfigError(fmt.Errorf("message scheduling cannot use discard new")))
// AllowRollup required.
_, err = addStreamPedanticWithError(t, nc, &StreamConfigRequest{
StreamConfig: StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: FileStorage,
AllowMsgSchedules: true,
AllowRollup: false,
},
Pedantic: true,
})
require_Error(t, err, NewJSStreamInvalidConfigError(fmt.Errorf("message scheduling cannot be set if roll-ups are disabled")))
}
func TestJetStreamScheduledPurgeHeadersRequiresSchedulingEnabled(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Storage: FileStorage,
Subjects: []string{"foo.*"},
AllowMsgSchedules: false,
})
require_NoError(t, err)
_, err = js.Publish("foo.victim", []byte("OK"))
require_NoError(t, err)
m := nats.NewMsg("foo.attacker")
m.Header.Set(JSScheduleNext, JSScheduleNextPurge)
m.Header.Set(JSScheduler, "foo.victim")
_, err = js.PublishMsg(m)
require_Error(t, err, NewJSMessageSchedulesDisabledError())
}
func TestJetStreamScheduledMessageNotDeactivated(t *testing.T) {
for _, storageType := range []StorageType{FileStorage, MemoryStorage} {
t.Run(storageType.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>"},
Storage: storageType,
AllowMsgSchedules: true,
})
require_NoError(t, err)
delay := func(d time.Duration) string {
return fmt.Sprintf("@at %s", time.Now().Add(d).Format(time.RFC3339Nano))
}
// Message should be scheduled.
m := nats.NewMsg("foo.schedule")
m.Header.Set("Nats-Schedule", delay(time.Second))
m.Header.Set("Nats-Schedule-Target", "foo.msg1")
_, err = js.PublishMsg(m)
require_NoError(t, err)
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
_, err = js.GetLastMsg("TEST", "foo.msg1")
if err != nil {
return err
}
return nil
})
// A message with a schedule is published.
m = nats.NewMsg("foo.schedule")
m.Header.Set("Nats-Schedule", delay(time.Second))
m.Header.Set("Nats-Schedule-Target", "foo.msg2")
_, err = js.PublishMsg(m)
require_NoError(t, err)
// But, a publish that is not a schedule should deactivate it.
_, err = js.Publish("foo.schedule", nil)
require_NoError(t, err)
// Wait for some time, and confirm the schedule wasn't triggered.
time.Sleep(1500 * time.Millisecond)
_, err = js.GetLastMsg("TEST", "foo.msg2")
require_Error(t, err, nats.ErrMsgNotFound)
})
}
}
func TestJetStreamScheduledMessageDeliveryPreservesTargetSchedule(t *testing.T) {
for _, storageType := range []StorageType{FileStorage, MemoryStorage} {
t.Run(storageType.String(), func(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := jsStreamCreate(t, nc, &StreamConfig{
Name: "TEST",
Subjects: []string{"foo.>"},
Storage: storageType,
AllowMsgSchedules: true,
})
require_NoError(t, err)
delay := func(d time.Duration) string {
return fmt.Sprintf("@at %s", time.Now().Add(d).Format(time.RFC3339Nano))
}
// Schedule on foo.A fires quickly and delivers onto foo.B.
mA := nats.NewMsg("foo.A")
mA.Header.Set("Nats-Schedule", delay(500*time.Millisecond))
mA.Header.Set("Nats-Schedule-Target", "foo.B")
_, err = js.PublishMsg(mA)
require_NoError(t, err)
// Independent schedule on foo.B fires later and delivers onto foo.C.
// Storing the fired delivery from foo.A onto foo.B must not tear
// down foo.B's own schedule.
mB := nats.NewMsg("foo.B")
mB.Header.Set("Nats-Schedule", delay(2*time.Second))
mB.Header.Set("Nats-Schedule-Target", "foo.C")
_, err = js.PublishMsg(mB)
require_NoError(t, err)
// Wait for the foo.A schedule to fire onto foo.B.
checkFor(t, 2*time.Second, 200*time.Millisecond, func() error {
_, err = js.GetLastMsg("TEST", "foo.B")
return err
})
// foo.B's schedule must still fire onto foo.C.
checkFor(t, 4*time.Second, 200*time.Millisecond, func() error {
_, err = js.GetLastMsg("TEST", "foo.C")
return err
})
})
}
}
func TestJetStreamScheduledMessageParse(t *testing.T) {
// @at <ts>
t.Run("@at", func(t *testing.T) {
ts := time.Now().UTC()
sts, repeat, ok := parseMsgSchedule(fmt.Sprintf("@at %s", ts.Format(time.RFC3339Nano)), nil, 0)
require_True(t, ok)
require_False(t, repeat)
require_Equal(t, ts, sts)
})
// @every <duration>
t.Run("@every", func(t *testing.T) {
now := time.Now().UTC().Round(time.Second)
sts, repeat, ok := parseMsgSchedule("@every 5s", nil, now.UnixNano())
require_True(t, ok)
require_True(t, repeat)
require_Equal(t, now.Add(5*time.Second), sts)
// A schedule on an interval should not spam loads of times if it hasn't run in a long while.
now = time.Now().UTC().Round(time.Second)
sts, repeat, ok = parseMsgSchedule("@every 5s", nil, 0)
require_True(t, ok)
require_True(t, repeat)
require_True(t, !sts.Before(now.Add(5*time.Second)))
// A schedule can only run at least once every second.
_, _, ok = parseMsgSchedule("@every 999ms", nil, 0)
require_False(t, ok)
})
// <cron> pattern
t.Run("cron", func(t *testing.T) {
now := time.Now().UTC().Round(time.Second)
sts, repeat, ok := parseMsgSchedule("* * * * * *", nil, now.UnixNano())
require_True(t, ok)
require_True(t, repeat)
require_Equal(t, now.Add(time.Second), sts)
// A schedule based on a cron should run the earliest "next" second.
now = time.Now().UTC().Truncate(time.Second).Add(time.Second - time.Nanosecond)
sts, repeat, ok = parseMsgSchedule("* * * * * *", nil, now.UnixNano())
require_True(t, ok)
require_True(t, repeat)
require_Equal(t, now.Truncate(time.Second).Add(time.Second), sts)
// A schedule based on cron should not spam loads of times if it hasn't run in a long while.
now = time.Now().UTC().Round(time.Second)
sts, repeat, ok = parseMsgSchedule("* * * * * *", nil, 0)
require_True(t, ok)
require_True(t, repeat)
require_True(t, !sts.Before(now.Add(time.Second)))
// Predefined cron patterns, e.g. @hourly.
for _, p := range []struct {
pattern string
delay func(time.Time) time.Time
}{
{pattern: "@yearly", delay: func(t time.Time) time.Time {
return t.AddDate(1, 0, 0)
}},
{pattern: "@annually", delay: func(t time.Time) time.Time {
return t.AddDate(1, 0, 0)
}},
{pattern: "@monthly", delay: func(t time.Time) time.Time {
return t.AddDate(0, 1, 0)
}},
{pattern: "@weekly", delay: func(t time.Time) time.Time {
return t.AddDate(0, 0, 7)
}},
{pattern: "@daily", delay: func(t time.Time) time.Time {
return t.AddDate(0, 0, 1)
}},
{pattern: "@midnight", delay: func(t time.Time) time.Time {
return t.AddDate(0, 0, 1)
}},
{pattern: "@hourly", delay: func(t time.Time) time.Time {
return t.Add(time.Hour)
}},
} {
t.Run(p.pattern, func(t *testing.T) {
// Skip ahead "current time", so we can test the added delay is correct.
now = time.Date(time.Now().Year()+2, 1, 1, 0, 0, 0, 0, time.UTC)
// Also ensure we're on a Sunday of this month, to test "@weekly".
if p.pattern == "@weekly" {
for now.Weekday() != 0 {
now = now.AddDate(0, 0, 1)
}
}
sts, repeat, ok = parseMsgSchedule(p.pattern, nil, now.UnixNano())
require_True(t, ok)
require_True(t, repeat)
require_Equal(t, p.delay(now), sts)
})
}
})
// <cron> pattern with time zone
t.Run("cron_tz", func(t *testing.T) {
loc, err := time.LoadLocation("Europe/Amsterdam")
require_NoError(t, err)
now := time.Now().UTC().Round(time.Second)
sts, repeat, ok := parseMsgSchedule("* * * * * *", loc, now.UnixNano())
require_True(t, ok)
require_True(t, repeat)
require_Equal(t, now.In(loc).Add(time.Second).String(), sts.String())
})
}
func TestJetStreamDirectGetBatchParallelWriteDeadlock(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Storage: nats.FileStorage,
AllowDirect: true,
})
require_NoError(t, err)
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
for range 2 {
require_NoError(t, mset.processJetStreamMsg("foo", _EMPTY_, nil, nil, 0, 0, nil, false, true))
}
// We'll lock the message blocks such that we can't read, but NumPending should still function.
fs := mset.store.(*fileStore)
fs.lockAllMsgBlocks()
total, validThrough, err := fs.NumPending(1, _EMPTY_, false)
require_NoError(t, err)
require_Equal(t, total, 2)
require_Equal(t, validThrough, 2)
// We'll now run things in the following order:
// - do a read through Direct Batch Get, which is blocked by the message blocks being locked
// - do a write in parallel, which is blocked by the read to complete
// - unlock the message blocks while both read and write goroutines are active
// If there's no deadlock the read and write will complete, and 3 messages will end up in the stream.
var wg, read sync.WaitGroup
read.Add(1)
wg.Add(1)
go func() {
read.Done()
mset.getDirectRequest(&JSApiMsgGetRequest{Seq: 1, Batch: 2}, _EMPTY_)
}()
go func() {
// Make sure we enter getDirectRequest first.
read.Wait()
<-time.After(100 * time.Millisecond)
wg.Done()
mset.processJetStreamMsg("foo", _EMPTY_, nil, nil, 0, 0, nil, false, true)
}()
go func() {
// Run some time after we've entered processJetStreamMsg above.
wg.Wait()
<-time.After(100 * time.Millisecond)
fs.unlockAllMsgBlocks()
}()
read.Wait()
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
if msgs := mset.state().Msgs; msgs != 3 {
return fmt.Errorf("expected 3 msgs, got %d", msgs)
}
return nil
})
}
func TestJetStreamReloadMetaCompact(t *testing.T) {
storeDir := t.TempDir()
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {
max_mem_store: 2MB
max_file_store: 8MB
store_dir: '%s'
}
`, storeDir)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
require_Equal(t, s.getOpts().JetStreamMetaCompact, 0)
reloadUpdateConfig(t, s, conf, fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {
max_mem_store: 2MB
max_file_store: 8MB
store_dir: '%s'
meta_compact: 100
}
`, storeDir))
require_Equal(t, s.getOpts().JetStreamMetaCompact, 100)
reloadUpdateConfig(t, s, conf, fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {
max_mem_store: 2MB
max_file_store: 8MB
store_dir: '%s'
meta_compact: 0
}
`, storeDir))
require_Equal(t, s.getOpts().JetStreamMetaCompact, 0)
}
// https://github.com/nats-io/nats-server/issues/7511
func TestJetStreamImplicitRePublishAfterSubjectTransform(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
cfg := &nats.StreamConfig{
Name: "TEST",
Subjects: []string{"a.>", "c.>"},
SubjectTransform: &nats.SubjectTransformConfig{Source: "a.>", Destination: "b.>"},
RePublish: &nats.RePublish{Destination: ">"}, // Implicitly RePublish 'b.>'.
}
// Forms a cycle since the RePublish captures both 'a.>' and 'c.>'
_, err := js.AddStream(cfg)
require_Error(t, err, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration for republish destination forms a cycle")))
// Doesn't form a cycle as 'a.>' is mapped to 'b.>'. A RePublish for '>' can be translated to 'b.>'.
cfg.Subjects = []string{"a.>"}
_, err = js.AddStream(cfg)
require_NoError(t, err)
sub, err := nc.SubscribeSync("b.>")
require_NoError(t, err)
defer sub.Drain()
// The published message should be transformed and RePublished.
_, err = js.Publish("a.hello", nil)
require_NoError(t, err)
msg, err := sub.NextMsg(time.Second)
require_NoError(t, err)
require_Equal(t, msg.Subject, "b.hello")
// Forms a cycle since the implicit RePublish on 'b.>' is lost.
// The RePublish would now mean publishing to 'c.>' which is a cycle.
cfg.Subjects = []string{"c.>"}
_, err = js.UpdateStream(cfg)
require_Error(t, err, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration for republish destination forms a cycle")))
}
func TestJetStreamStreamMirrorWithoutDuplicateWindow(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Duplicates: time.Second,
})
require_NoError(t, err)
pubAck, err := js.Publish("foo", nil, nats.MsgId("msgId"))
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
require_False(t, pubAck.Duplicate)
pubAck, err = js.Publish("foo", nil, nats.MsgId("msgId"))
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
require_True(t, pubAck.Duplicate)
time.Sleep(1200 * time.Millisecond)
pubAck, err = js.Publish("foo", nil, nats.MsgId("msgId"))
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 2)
require_False(t, pubAck.Duplicate)
_, err = js.AddStream(&nats.StreamConfig{
Name: "M",
Mirror: &nats.StreamSource{Name: "TEST"},
Duplicates: 0,
})
require_NoError(t, err)
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
mset, err := s.globalAccount().lookupStream("M")
if err != nil {
return err
}
state := mset.state()
if state.Msgs != 2 || state.FirstSeq != 1 || state.LastSeq != 2 {
return fmt.Errorf("incorrect state: %v", state)
}
return nil
})
}
func TestJetStreamStreamMirrorIgnoresUpstreamDuplicateMsgIds(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Storage: nats.FileStorage,
Subjects: []string{"foo"},
Duplicates: 100 * time.Millisecond,
})
require_NoError(t, err)
pubAck, err := js.Publish("foo", nil, nats.MsgId("X"))
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
require_False(t, pubAck.Duplicate)
// Wait for upstream's dedup window to expire so the next publish lands as
// a fresh message at sseq=2 with the same Nats-Msg-Id.
time.Sleep(200 * time.Millisecond)
pubAck, err = js.Publish("foo", nil, nats.MsgId("X"))
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 2)
require_False(t, pubAck.Duplicate)
// Mirror must replicate both messages even though they share Nats-Msg-Id.
_, err = js.AddStream(&nats.StreamConfig{
Name: "M",
Storage: nats.FileStorage,
Mirror: &nats.StreamSource{Name: "TEST"},
Duplicates: 2 * time.Minute,
})
require_NoError(t, err)
checkFor(t, 2*time.Second, 50*time.Millisecond, func() error {
mset, err := s.globalAccount().lookupStream("M")
if err != nil {
return err
}
state := mset.state()
if state.Msgs != 2 || state.LastSeq != 2 {
return fmt.Errorf("incorrect state: %v", state)
}
return nil
})
// Mirror must keep advancing, not be stuck.
pubAck, err = js.Publish("foo", nil, nats.MsgId("Y"))
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 3)
checkFor(t, 2*time.Second, 50*time.Millisecond, func() error {
mset, err := s.globalAccount().lookupStream("M")
if err != nil {
return err
}
state := mset.state()
if state.Msgs != 3 || state.LastSeq != 3 {
return fmt.Errorf("incorrect state: %v", state)
}
return nil
})
// After restart, rebuildDedupe repopulates ddmap from stored messages,
// but the mirror must still ignore those entries for incoming upstream msgs.
sd := s.JetStreamConfig().StoreDir
nc.Close()
s.Shutdown()
s.WaitForShutdown()
s = RunJetStreamServerOnPort(-1, sd)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
// Wait for upstream's dedup window to expire again, then re-publish "X".
time.Sleep(200 * time.Millisecond)
pubAck, err = js.Publish("foo", nil, nats.MsgId("X"))
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 4)
require_False(t, pubAck.Duplicate)
checkFor(t, 2*time.Second, 50*time.Millisecond, func() error {
mset, err := s.globalAccount().lookupStream("M")
if err != nil {
return err
}
state := mset.state()
if state.Msgs != 4 || state.LastSeq != 4 {
return fmt.Errorf("incorrect state: %v", state)
}
return nil
})
}
func TestJetStreamStreamSourceWithoutDuplicateWindow(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
Duplicates: time.Second,
})
require_NoError(t, err)
pubAck, err := js.Publish("foo", nil, nats.MsgId("msgId"))
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
require_False(t, pubAck.Duplicate)
pubAck, err = js.Publish("foo", nil, nats.MsgId("msgId"))
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
require_True(t, pubAck.Duplicate)
time.Sleep(1200 * time.Millisecond)
pubAck, err = js.Publish("foo", nil, nats.MsgId("msgId"))
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 2)
require_False(t, pubAck.Duplicate)
_, err = js.AddStream(&nats.StreamConfig{
Name: "S",
Sources: []*nats.StreamSource{{Name: "TEST"}},
Duplicates: 0,
})
require_NoError(t, err)
checkFor(t, 2*time.Second, 100*time.Millisecond, func() error {
mset, err := s.globalAccount().lookupStream("S")
if err != nil {
return err
}
state := mset.state()
if state.Msgs != 2 || state.FirstSeq != 1 || state.LastSeq != 2 {
return fmt.Errorf("incorrect state: %v", state)
}
return nil
})
}
func TestJetStreamServerEncryptionRecoveryWithoutStreamStateFile(t *testing.T) {
cases := []struct {
name string
cstr string
cipher StoreCipher
}{
{"Default", _EMPTY_, ChaCha},
{"ChaCha", ", cipher: chacha", ChaCha},
{"AES", ", cipher: aes", AES},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
tmpl := `
server_name: S22
listen: 127.0.0.1:-1
jetstream: {key: $JS_KEY, store_dir: '%s' %s}
`
storeDir := t.TempDir()
conf := createConfFile(t, []byte(fmt.Sprintf(tmpl, storeDir, c.cstr)))
os.Setenv("JS_KEY", "s3cr3t!!")
defer os.Unsetenv("JS_KEY")
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
config := s.JetStreamConfig()
if config == nil {
t.Fatalf("Expected config but got none")
}
defer removeDir(t, config.StoreDir)
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"foo"}})
require_NoError(t, err)
_, err = js.Publish("foo", nil)
require_NoError(t, err)
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
before := si.State
require_Equal(t, before.Msgs, 1)
require_Equal(t, before.FirstSeq, 1)
require_Equal(t, before.LastSeq, 1)
for i := range 2 {
s.Shutdown()
s.WaitForShutdown()
// Previously, the server would rely on this file to be present. If it wasn't, it would
// not initialize the keys and erroneously regenerate the meta.key upon the next graceful
// shutdown. A subsequent restart would not allow this stream to be loaded.
if i == 0 {
stateFile := filepath.Join(storeDir, JetStreamStoreDir, globalAccountName, streamsDir, "TEST", msgDir, streamStreamStateFile)
require_NoError(t, os.Remove(stateFile))
}
s, _ = RunServerWithConfig(conf)
defer s.Shutdown()
// Reconnect.
nc.Close()
nc, js = jsClientConnect(t, s)
defer nc.Close()
// Previously, the next iteration would fail by not finding the stream.
si, err = js.StreamInfo("TEST")
require_NoError(t, err)
if state := si.State; !reflect.DeepEqual(state, before) {
t.Fatalf("Expected state\n of %+v, \ngot %+v without index.db state", before, state)
}
}
})
}
}
func TestJetStreamFileStoreErrorOpeningBlockAfterTruncate(t *testing.T) {
storeDir := t.TempDir()
conf := createConfFile(t, []byte(fmt.Sprintf(`
listen: 127.0.0.1:-1
jetstream: {store_dir: %q}
`, storeDir)))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"foo"},
})
require_NoError(t, err)
pubAck, err := js.Publish("foo", nil)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
// Shut down the server and manually truncate the message blocks to be entirely empty, simulating data loss.
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
fs := mset.store.(*fileStore)
blk := filepath.Join(fs.fcfg.StoreDir, msgDir, "1.blk")
index := filepath.Join(fs.fcfg.StoreDir, msgDir, streamStreamStateFile)
nc.Close()
s.Shutdown()
// Truncate the block such that it isn't fully empty, but doesn't contain any messages.
require_NoError(t, os.Truncate(blk, 1))
require_NoError(t, os.Remove(index))
// Restart the server and reconnect.
s, _ = RunServerWithConfig(conf)
defer s.Shutdown()
nc, js = jsClientConnect(t, s)
defer nc.Close()
// Publish another message. Due to the simulated data loss, the stream sequence should continue
// counting after truncating the corrupted data.
pubAck, err = js.Publish("foo", nil)
require_NoError(t, err)
require_Equal(t, pubAck.Sequence, 1)
}
func TestJetStreamSourceConfigValidation(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc := clientConnectToServer(t, s)
defer nc.Close()
// Not testing with js.AddStream as passing it a nil source causes it to panic.
msg := nats.Msg{Subject: "$JS.API.STREAM.CREATE.crash", Data: []byte(`{"name":"crash","retention":"limits","max_consumers":-1,"max_msgs_per_subject":-1,"max_msgs":-1,"max_bytes":-1,"max_age":0,"max_msg_size":-1,"storage":"file","discard":"old","num_replicas":1,"duplicate_window":120000000000,"sources":[null],"sealed":false,"deny_delete":false,"deny_purge":false,"allow_rollup_hdrs":false,"allow_direct":true,"mirror_direct":false,"consumer_limits":{}}`)}
response, err := nc.Request(msg.Subject, msg.Data, time.Second)
require_NoError(t, err)
require_Equal(t, string(response.Data), `{"type":"io.nats.jetstream.api.v1.stream_create_response","error":{"code":400,"err_code":10141,"description":"sourced stream name is invalid"}}`)
}
// https://github.com/nats-io/nats-server/issues/6747
func TestJetStreamCleanupNoInterestAboveThreshold(t *testing.T) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"a", "b"},
Retention: nats.InterestPolicy,
})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "A", FilterSubject: "a"})
require_NoError(t, err)
_, err = js.AddConsumer("TEST", &nats.ConsumerConfig{Durable: "B", FilterSubject: "b"})
require_NoError(t, err)
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
for _, subj := range []string{"a", "b"} {
for range 100_000 {
_, _, err = mset.store.StoreMsg(subj, nil, nil, 0)
require_NoError(t, err)
}
}
si, err := js.StreamInfo("TEST")
require_NoError(t, err)
require_Equal(t, si.State.Msgs, 200_000)
// Deleting the consumer will trigger deletion of the messages that now have no interest.
// Since the total number of messages in the stream is above the threshold, this might take some time.
require_NoError(t, js.DeleteConsumer("TEST", "A"))
checkFor(t, 4*time.Second, 200*time.Millisecond, func() error {
si, err = js.StreamInfo("TEST")
if err != nil {
return err
}
if si.State.Msgs != 100_000 {
return fmt.Errorf("expected 100k messages, got %d", si.State.Msgs)
}
return nil
})
}
func TestJetStreamStoreFilterIsAll(t *testing.T) {
test := func(t *testing.T, storage nats.StorageType) {
s := RunBasicJetStreamServer(t)
defer s.Shutdown()
nc, js := jsClientConnect(t, s)
defer nc.Close()
_, err := js.AddStream(&nats.StreamConfig{
Name: "TEST",
Subjects: []string{"c", "b", "a"},
Storage: storage,
})
require_NoError(t, err)
mset, err := s.globalAccount().lookupStream("TEST")
require_NoError(t, err)
if fs, ok := mset.store.(*fileStore); ok {
require_True(t, fs.filterIsAll([]string{"b", "a", "c"}))
} else if ms, ok := mset.store.(*memStore); ok {
require_True(t, ms.filterIsAll([]string{"b", "a", "c"}))
} else {
t.Fatal("unknown store type")
}
}
t.Run("Memory", func(t *testing.T) { test(t, nats.MemoryStorage) })
t.Run("File", func(t *testing.T) { test(t, nats.FileStorage) })
}
// Test that flow control works correctly when a stream sources from two
// different external accounts. Each account's $JS.FC.> service import
// causes FC replies to fan out to all accounts, but only the correct
// account's consumer handles the reply. This verifies the fan-out
// doesn't prevent FC from completing.
func TestJetStreamFlowControlCrossAccountFanOut(t *testing.T) {
conf := createConfFile(t, fmt.Appendf(nil, `
listen: "127.0.0.1:-1"
jetstream: {store_dir: %q}
accounts: {
ACCT_A: {
jetstream: enabled
users: [{user: usrA, password: pwd}]
exports: [
{service: "$JS.API.CONSUMER.>"}
{stream: "RI.DELIVER.A.>"}
{service: "$JS.FC.>"}
]
}
ACCT_B: {
jetstream: enabled
users: [{user: usrB, password: pwd}]
exports: [
{service: "$JS.API.CONSUMER.>"}
{stream: "RI.DELIVER.B.>"}
{service: "$JS.FC.>"}
]
}
SOURCER: {
jetstream: enabled
users: [{user: src, password: pwd}]
imports: [
{service: {account: ACCT_A, subject: "$JS.API.CONSUMER.>"}, to: "RI.JS.A.API.CONSUMER.>"}
{stream: {account: ACCT_A, subject: "RI.DELIVER.A.>"}}
{service: {account: ACCT_A, subject: "$JS.FC.>"}}
{service: {account: ACCT_B, subject: "$JS.API.CONSUMER.>"}, to: "RI.JS.B.API.CONSUMER.>"}
{stream: {account: ACCT_B, subject: "RI.DELIVER.B.>"}}
{service: {account: ACCT_B, subject: "$JS.FC.>"}}
]
}
$SYS {users: [{user: admin, password: pwd}]}
}
`, t.TempDir()))
s, _ := RunServerWithConfig(conf)
defer s.Shutdown()
// Create source streams in each account.
ncA, jsA := jsClientConnect(t, s, nats.UserInfo("usrA", "pwd"))
defer ncA.Close()
_, err := jsA.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"A"}})
require_NoError(t, err)
ncB, jsB := jsClientConnect(t, s, nats.UserInfo("usrB", "pwd"))
defer ncB.Close()
_, err = jsB.AddStream(&nats.StreamConfig{Name: "TEST", Subjects: []string{"B"}})
require_NoError(t, err)
// Publish 10 x 256KB messages to each source. Total 2.56MB exceeds the
// initial FC threshold (maxpb/2 = 1MB), triggering at least one FC
// round-trip per source.
payload := []byte(strings.Repeat("X", 256*1024))
for i := 0; i < 10; i++ {
_, err = jsA.Publish("A", payload)
require_NoError(t, err)
_, err = jsB.Publish("B", payload)
require_NoError(t, err)
}
// Create sourcing stream that pulls from both accounts.
ncS, jsS := jsClientConnect(t, s, nats.UserInfo("src", "pwd"))
defer ncS.Close()
_, err = jsS.AddStream(&nats.StreamConfig{
Name: "DEST",
Sources: []*nats.StreamSource{
{
Name: "TEST",
External: &nats.ExternalStream{
APIPrefix: "RI.JS.A.API",
DeliverPrefix: "RI.DELIVER.A",
},
},
{
Name: "TEST",
External: &nats.ExternalStream{
APIPrefix: "RI.JS.B.API",
DeliverPrefix: "RI.DELIVER.B",
},
},
},
})
require_NoError(t, err)
// Wait for all 20 messages to arrive.
checkFor(t, 10*time.Second, 250*time.Millisecond, func() error {
si, err := jsS.StreamInfo("DEST")
if err != nil {
return err
}
if si.State.Msgs != 20 {
return fmt.Errorf("expected 20 msgs, got %d", si.State.Msgs)
}
return nil
})
// Verify both sources converged with zero lag.
si, err := jsS.StreamInfo("DEST")
require_NoError(t, err)
require_Equal(t, len(si.Sources), 2)
for _, src := range si.Sources {
require_Equal(t, src.Lag, 0)
}
// Verify FC was exercised by checking that the internal consumers on
// each source account ramped up their maxpb beyond the initial value.
for _, accName := range []string{"ACCT_A", "ACCT_B"} {
acc, err := s.lookupAccount(accName)
require_NoError(t, err)
mset, err := acc.lookupStream("TEST")
require_NoError(t, err)
consumers := mset.getConsumers()
require_Equal(t, len(consumers), 1)
o := consumers[0]
o.mu.RLock()
maxpb := o.maxpb
pblimit := o.pblimit
o.mu.RUnlock()
if maxpb <= pblimit/16 {
t.Fatalf("account %s: expected FC ramp-up (maxpb=%d > initial=%d)", accName, maxpb, pblimit/16)
}
}
}