mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-14 10:10:42 -07:00
3318 lines
95 KiB
Go
3318 lines
95 KiB
Go
// Copyright 2019 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.
|
|
|
|
package test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"math/rand"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats-server/v2/server"
|
|
"github.com/nats-io/nats-server/v2/server/sysmem"
|
|
"github.com/nats-io/nats.go"
|
|
"github.com/nats-io/nuid"
|
|
)
|
|
|
|
func TestJetStreamBasicNilConfig(t *testing.T) {
|
|
s := RunRandClientPortServer()
|
|
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 {
|
|
// 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)
|
|
}
|
|
}
|
|
// Check store path, should be tmpdir.
|
|
expectedDir := filepath.Join(os.TempDir(), server.JetStreamStoreDir)
|
|
if config.StoreDir != expectedDir {
|
|
t.Fatalf("Expected storage directory of %q, but got %q", expectedDir, config.StoreDir)
|
|
}
|
|
// 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() *server.Server {
|
|
opts := DefaultTestOptions
|
|
opts.Port = -1
|
|
opts.JetStream = true
|
|
return RunServer(&opts)
|
|
}
|
|
|
|
func clientConnectToServer(t *testing.T, s *server.Server) *nats.Conn {
|
|
nc, err := nats.Connect(s.ClientURL())
|
|
if err != nil {
|
|
t.Fatalf("Failed to create client: %v", err)
|
|
}
|
|
return nc
|
|
}
|
|
|
|
func TestJetStreamEnableAndDisableAccount(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
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", server.FriendlyBytes(rm), server.FriendlyBytes(rd))
|
|
}
|
|
|
|
acc, _ := s.LookupOrRegisterAccount("$FOO")
|
|
if err := acc.EnableJetStream(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 = server.NewAccount("$BAZ")
|
|
if err := acc.EnableJetStream(nil); err == nil {
|
|
t.Fatalf("Expected error on enabling account that was not registered")
|
|
}
|
|
}
|
|
|
|
func TestJetStreamAddMsgSet(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{name: "MemoryStore",
|
|
mconfig: &server.MsgSetConfig{
|
|
Name: "foo",
|
|
Retention: server.StreamPolicy,
|
|
MaxAge: time.Hour,
|
|
Storage: server.MemoryStorage,
|
|
Replicas: 1,
|
|
}},
|
|
{name: "FileStore",
|
|
mconfig: &server.MsgSetConfig{
|
|
Name: "foo",
|
|
Retention: server.StreamPolicy,
|
|
MaxAge: time.Hour,
|
|
Storage: server.FileStorage,
|
|
Replicas: 1,
|
|
}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
nc.Publish("foo", []byte("Hello World!"))
|
|
nc.Flush()
|
|
|
|
stats := mset.Stats()
|
|
if stats.Msgs != 1 {
|
|
t.Fatalf("Expected 1 message, got %d", stats.Msgs)
|
|
}
|
|
if stats.Bytes == 0 {
|
|
t.Fatalf("Expected non-zero bytes")
|
|
}
|
|
|
|
nc.Publish("foo", []byte("Hello World Again!"))
|
|
nc.Flush()
|
|
|
|
stats = mset.Stats()
|
|
if stats.Msgs != 2 {
|
|
t.Fatalf("Expected 2 messages, got %d", stats.Msgs)
|
|
}
|
|
|
|
if err := mset.Delete(); err != nil {
|
|
t.Fatalf("Got an error deleting the message set: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func expectOKResponse(t *testing.T, m *nats.Msg) {
|
|
t.Helper()
|
|
if m == nil {
|
|
t.Fatalf("No response, possible timeout?")
|
|
}
|
|
if string(m.Data) != server.OK {
|
|
t.Fatalf("Expected a JetStreamPubAck, got %q", m.Data)
|
|
}
|
|
}
|
|
|
|
func TestJetStreamBasicAckPublish(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "foo", Storage: server.MemoryStorage, Subjects: []string{"foo.*"}}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "foo", Storage: server.FileStorage, Subjects: []string{"foo.*"}}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
for i := 0; i < 50; i++ {
|
|
resp, _ := nc.Request("foo.bar", []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
stats := mset.Stats()
|
|
if stats.Msgs != 50 {
|
|
t.Fatalf("Expected 50 messages, got %d", stats.Msgs)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamNoAckMsgSet(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "foo", Storage: server.MemoryStorage, NoAck: true}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "foo", Storage: server.FileStorage, NoAck: true}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
// We can use NoAck to suppress acks even when reply subjects are present.
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %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")
|
|
}
|
|
|
|
stats := mset.Stats()
|
|
if stats.Msgs != 1 {
|
|
t.Fatalf("Expected 1 message, got %d", stats.Msgs)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamCreateObservable(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "foo", Storage: server.MemoryStorage, Subjects: []string{"foo", "bar"}}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "foo", Storage: server.FileStorage, Subjects: []string{"foo", "bar"}}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
// Check for basic errors.
|
|
if _, err := mset.AddObservable(nil); err == nil {
|
|
t.Fatalf("Expected an error for no config")
|
|
}
|
|
|
|
// No deliver subject, meaning its in pull mode, work queue mode means it is required to
|
|
// do explicit ack.
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{}); err == nil {
|
|
t.Fatalf("Expected an error on work queue / pull mode without explicit ack mode")
|
|
}
|
|
|
|
// Check for delivery subject errors.
|
|
|
|
// Literal delivery subject required.
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{Delivery: "foo.*"}); err == nil {
|
|
t.Fatalf("Expected an error on bad delivery subject")
|
|
}
|
|
// Check for cycles
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{Delivery: "foo"}); err == nil {
|
|
t.Fatalf("Expected an error on delivery subject that forms a cycle")
|
|
}
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{Delivery: "bar"}); err == nil {
|
|
t.Fatalf("Expected an error on delivery subject that forms a cycle")
|
|
}
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{Delivery: "*"}); err == nil {
|
|
t.Fatalf("Expected an error on delivery subject that forms a cycle")
|
|
}
|
|
|
|
// StartPosition conflicts
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{
|
|
Delivery: "A",
|
|
MsgSetSeq: 1,
|
|
StartTime: time.Now(),
|
|
}); err == nil {
|
|
t.Fatalf("Expected an error on start position conflicts")
|
|
}
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{
|
|
Delivery: "A",
|
|
StartTime: time.Now(),
|
|
DeliverAll: true,
|
|
}); err == nil {
|
|
t.Fatalf("Expected an error on start position conflicts")
|
|
}
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{
|
|
Delivery: "A",
|
|
DeliverAll: true,
|
|
DeliverLast: true,
|
|
}); err == nil {
|
|
t.Fatalf("Expected an error on start position conflicts")
|
|
}
|
|
|
|
// Non-Durables need to have subscription to delivery subject.
|
|
delivery := nats.NewInbox()
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{Delivery: delivery}); err == nil {
|
|
t.Fatalf("Expected an error on unsubscribed delivery subject")
|
|
}
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
sub, _ := nc.SubscribeSync(delivery)
|
|
defer sub.Unsubscribe()
|
|
nc.Flush()
|
|
|
|
// Subjects can not be AckAll.
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{
|
|
Delivery: delivery,
|
|
DeliverAll: true,
|
|
Subject: "foo",
|
|
AckPolicy: server.AckAll,
|
|
}); err == nil {
|
|
t.Fatalf("Expected an error on partitioned observable with ack policy of all")
|
|
}
|
|
|
|
// This should work..
|
|
o, err := mset.AddObservable(&server.ObservableConfig{Delivery: delivery})
|
|
if err != nil {
|
|
t.Fatalf("Expected no error with registered interest, got %v", err)
|
|
}
|
|
|
|
if err := mset.DeleteObservable(o); err != nil {
|
|
t.Fatalf("Expected no error on delete, got %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamBasicDelivery(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "MSET", Storage: server.MemoryStorage, Subjects: []string{"foo.*"}}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "MSET", Storage: server.FileStorage, Subjects: []string{"foo.*"}}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
toSend := 100
|
|
sendSubj := "foo.bar"
|
|
for i := 1; i <= toSend; i++ {
|
|
payload := strconv.Itoa(i)
|
|
resp, _ := nc.Request(sendSubj, []byte(payload), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
stats := mset.Stats()
|
|
if stats.Msgs != uint64(toSend) {
|
|
t.Fatalf("Expected %d messages, got %d", toSend, stats.Msgs)
|
|
}
|
|
|
|
// Now create an observable. Use different connection.
|
|
nc2 := clientConnectToServer(t, s)
|
|
defer nc2.Close()
|
|
|
|
sub, _ := nc2.SubscribeSync(nats.NewInbox())
|
|
defer sub.Unsubscribe()
|
|
nc2.Flush()
|
|
|
|
o, err := mset.AddObservable(&server.ObservableConfig{Delivery: sub.Subject, DeliverAll: true})
|
|
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++ {
|
|
payload := strconv.Itoa(i)
|
|
resp, _ := nc.Request(sendSubj, []byte(payload), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
stats = mset.Stats()
|
|
if stats.Msgs != uint64(toSend*2) {
|
|
t.Fatalf("Expected %d messages, got %d", toSend*2, stats.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.AddObservable(&server.ObservableConfig{Delivery: sub.Subject, DeliverLast: true})
|
|
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 Observables 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 is the last msg we sent though.
|
|
if mseq, _ := strconv.Atoi(string(m.Data)); mseq != 200 {
|
|
t.Fatalf("Expected messag sequence to be 200, but got %d", mseq)
|
|
}
|
|
|
|
checkSubEmpty()
|
|
o.Delete()
|
|
|
|
o, err = mset.AddObservable(&server.ObservableConfig{Delivery: sub.Subject}) // Default is deliver new only.
|
|
if err != nil {
|
|
t.Fatalf("Expected no error with registered interest, got %v", err)
|
|
}
|
|
defer 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.AddObservable(&server.ObservableConfig{Delivery: sub.Subject, MsgSetSeq: 101})
|
|
if err != nil {
|
|
t.Fatalf("Expected no error with registered interest, got %v", err)
|
|
}
|
|
defer o.Delete()
|
|
|
|
checkMsgs(1)
|
|
})
|
|
}
|
|
}
|
|
|
|
func workerModeConfig(name string) *server.ObservableConfig {
|
|
return &server.ObservableConfig{Durable: name, DeliverAll: true, AckPolicy: server.AckExplicit}
|
|
}
|
|
|
|
func TestJetStreamBasicWorkQueue(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "MY_MSG_SET", Storage: server.MemoryStorage, Subjects: []string{"foo", "bar"}}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "MY_MSG_SET", Storage: server.FileStorage, Subjects: []string{"foo", "bar"}}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
// Create basic work queue mode observable.
|
|
oname := "WQ"
|
|
o, err := mset.AddObservable(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++ {
|
|
resp, _ := nc.Request(sendSubj, []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
stats := mset.Stats()
|
|
if stats.Msgs != uint64(toSend) {
|
|
t.Fatalf("Expected %d messages, got %d", toSend, stats.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)
|
|
nc.Request(sendSubj, []byte("Hello World!"), 100*time.Millisecond)
|
|
}()
|
|
|
|
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)
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
}()
|
|
|
|
for i := toSend + 2; i < toSend*2+2; i++ {
|
|
getNext(i)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamSubjecting(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "MSET", Storage: server.MemoryStorage, Subjects: []string{"foo.*"}}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "MSET", Storage: server.FileStorage, Subjects: []string{"foo.*"}}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %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++ {
|
|
resp, _ := nc.Request(subjA, []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
resp, _ = nc.Request(subjB, []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
stats := mset.Stats()
|
|
if stats.Msgs != uint64(toSend*2) {
|
|
t.Fatalf("Expected %d messages, got %d", toSend*2, stats.Msgs)
|
|
}
|
|
|
|
delivery := nats.NewInbox()
|
|
sub, _ := nc.SubscribeSync(delivery)
|
|
defer sub.Unsubscribe()
|
|
nc.Flush()
|
|
|
|
o, err := mset.AddObservable(&server.ObservableConfig{Delivery: delivery, Subject: subjB, DeliverAll: true})
|
|
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)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", 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 TestJetStreamWorkQueueSubjecting(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "MY_MSG_SET", Storage: server.MemoryStorage, Subjects: []string{"foo.*"}}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "MY_MSG_SET", Storage: server.FileStorage, Subjects: []string{"foo.*"}}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %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++ {
|
|
resp, _ := nc.Request(subjA, []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
resp, _ = nc.Request(subjB, []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
stats := mset.Stats()
|
|
if stats.Msgs != uint64(toSend*2) {
|
|
t.Fatalf("Expected %d messages, got %d", toSend*2, stats.Msgs)
|
|
}
|
|
|
|
oname := "WQ"
|
|
o, err := mset.AddObservable(&server.ObservableConfig{Durable: oname, Subject: subjA, DeliverAll: true, AckPolicy: server.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)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", 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 TestJetStreamWorkQueueAckAndNext(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "MY_MSG_SET", Storage: server.MemoryStorage, Subjects: []string{"foo", "bar"}}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "MY_MSG_SET", Storage: server.FileStorage, Subjects: []string{"foo", "bar"}}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
// Create basic work queue mode observable.
|
|
oname := "WQ"
|
|
o, err := mset.AddObservable(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++ {
|
|
resp, _ := nc.Request(sendSubj, []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
stats := mset.Stats()
|
|
if stats.Msgs != uint64(toSend) {
|
|
t.Fatalf("Expected %d messages, got %d", toSend, stats.Msgs)
|
|
}
|
|
|
|
sub, _ := nc.SubscribeSync(nats.NewInbox())
|
|
defer sub.Unsubscribe()
|
|
|
|
// Kick things off.
|
|
// For normal work queue semantics, you send requests to the subject with message set and observable 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)
|
|
}
|
|
nc.PublishRequest(m.Reply, sub.Subject, server.AckNext)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamWorkQueueRequestBatch(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "MY_MSG_SET", Storage: server.MemoryStorage, Subjects: []string{"foo", "bar"}}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "MY_MSG_SET", Storage: server.FileStorage, Subjects: []string{"foo", "bar"}}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
// Create basic work queue mode observable.
|
|
oname := "WQ"
|
|
o, err := mset.AddObservable(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++ {
|
|
resp, _ := nc.Request(sendSubj, []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
stats := mset.Stats()
|
|
if stats.Msgs != uint64(toSend) {
|
|
t.Fatalf("Expected %d messages, got %d", toSend, stats.Msgs)
|
|
}
|
|
|
|
sub, _ := nc.SubscribeSync(nats.NewInbox())
|
|
defer sub.Unsubscribe()
|
|
|
|
// For normal work queue semantics, you send requests to the subject with message set and observable 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
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamWorkQueueRetentionMsgSet(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{name: "MemoryStore", mconfig: &server.MsgSetConfig{
|
|
Name: "MWQ",
|
|
Storage: server.MemoryStorage,
|
|
Subjects: []string{"MY_WORK_QUEUE.*"},
|
|
Retention: server.WorkQueuePolicy},
|
|
},
|
|
{name: "FileStore", mconfig: &server.MsgSetConfig{
|
|
Name: "MWQ",
|
|
Storage: server.FileStorage,
|
|
Subjects: []string{"MY_WORK_QUEUE.*"},
|
|
Retention: server.WorkQueuePolicy},
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
sub, _ := nc.SubscribeSync(nats.NewInbox())
|
|
defer sub.Unsubscribe()
|
|
nc.Flush()
|
|
|
|
// This type of message set has restrictions which we will test here.
|
|
|
|
// Push based not allowed.
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{Delivery: sub.Subject}); err == nil {
|
|
t.Fatalf("Expected an error on delivery subject")
|
|
}
|
|
|
|
// DeliverAll is only start mode allowed.
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{DeliverLast: true}); err == nil {
|
|
t.Fatalf("Expected an error with anything but DeliverAll")
|
|
}
|
|
|
|
// We will create a non-partitioned observable. This should succeed.
|
|
o, err := mset.AddObservable(&server.ObservableConfig{DeliverAll: true, AckPolicy: server.AckExplicit})
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
defer o.Delete()
|
|
|
|
// Now if we create another this should fail, only can have one non-partitioned.
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{DeliverAll: true}); err == nil {
|
|
t.Fatalf("Expected an error on attempt for second observable for a workqueue")
|
|
}
|
|
o.Delete()
|
|
|
|
if numo := mset.NumObservables(); numo != 0 {
|
|
t.Fatalf("Expected to have zero observables, got %d", numo)
|
|
}
|
|
|
|
// Now add in an observable that has a partition.
|
|
pConfig := func(pname string) *server.ObservableConfig {
|
|
return &server.ObservableConfig{DeliverAll: true, Subject: pname, AckPolicy: server.AckExplicit}
|
|
}
|
|
o, err = mset.AddObservable(pConfig("MY_WORK_QUEUE.A"))
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
defer o.Delete()
|
|
|
|
// Now creating another with separate partition should work.
|
|
o2, err := mset.AddObservable(pConfig("MY_WORK_QUEUE.B"))
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
defer o2.Delete()
|
|
|
|
// Anything that would overlap should fail though.
|
|
if _, err := mset.AddObservable(pConfig(">")); err == nil {
|
|
t.Fatalf("Expected an error on attempt for partitioned observable for a workqueue")
|
|
}
|
|
if _, err := mset.AddObservable(pConfig("MY_WORK_QUEUE.A")); err == nil {
|
|
t.Fatalf("Expected an error on attempt for partitioned observable for a workqueue")
|
|
}
|
|
if _, err := mset.AddObservable(pConfig("MY_WORK_QUEUE.A")); err == nil {
|
|
t.Fatalf("Expected an error on attempt for partitioned observable for a workqueue")
|
|
}
|
|
|
|
o3, err := mset.AddObservable(pConfig("MY_WORK_QUEUE.C"))
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", err)
|
|
}
|
|
defer o3.Delete()
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamWorkQueueAckWaitRedelivery(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "MY_WQ", Storage: server.MemoryStorage, Retention: server.WorkQueuePolicy}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "MY_WQ", Storage: server.FileStorage, Retention: server.WorkQueuePolicy}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %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++ {
|
|
resp, _ := nc.Request(c.mconfig.Name, []byte("Hello World!"), 100*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
stats := mset.Stats()
|
|
if stats.Msgs != uint64(toSend) {
|
|
t.Fatalf("Expected %d messages, got %d", toSend, stats.Msgs)
|
|
}
|
|
|
|
ackWait := 100 * time.Millisecond
|
|
|
|
o, err := mset.AddObservable(&server.ObservableConfig{DeliverAll: true, AckPolicy: server.AckExplicit, AckWait: ackWait})
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", 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.
|
|
stats = mset.Stats()
|
|
if int(stats.Msgs) != toSend {
|
|
t.Fatalf("Expected %d messages, got %d", toSend, stats.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 messages: %v", err)
|
|
}
|
|
sseq, dseq, dcount := o.ReplyInfo(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.Respond(nil)
|
|
}
|
|
|
|
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 stats := mset.Stats(); stats.Msgs != 0 {
|
|
t.Fatalf("Expected no messages, got %d", stats.Msgs)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamWorkQueueNakRedelivery(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "MY_WQ", Storage: server.MemoryStorage, Retention: server.WorkQueuePolicy}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "MY_WQ", Storage: server.FileStorage, Retention: server.WorkQueuePolicy}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %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++ {
|
|
resp, _ := nc.Request(c.mconfig.Name, []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
stats := mset.Stats()
|
|
if stats.Msgs != uint64(toSend) {
|
|
t.Fatalf("Expected %d messages, got %d", toSend, stats.Msgs)
|
|
}
|
|
|
|
o, err := mset.AddObservable(&server.ObservableConfig{DeliverAll: true, AckPolicy: server.AckExplicit})
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", 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, _ := o.ReplyInfo(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.
|
|
m.Respond(server.AckNak)
|
|
|
|
// 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 *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "MY_WQ", Storage: server.MemoryStorage, Retention: server.WorkQueuePolicy}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "MY_WQ", Storage: server.FileStorage, Retention: server.WorkQueuePolicy}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %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++ {
|
|
resp, _ := nc.Request(c.mconfig.Name, []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
stats := mset.Stats()
|
|
if stats.Msgs != uint64(toSend) {
|
|
t.Fatalf("Expected %d messages, got %d", toSend, stats.Msgs)
|
|
}
|
|
|
|
ackWait := 50 * time.Millisecond
|
|
|
|
o, err := mset.AddObservable(&server.ObservableConfig{DeliverAll: true, AckPolicy: server.AckExplicit, AckWait: ackWait})
|
|
if err != nil {
|
|
t.Fatalf("Expected no error, got %v", 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, _ := o.ReplyInfo(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)
|
|
m.Respond(server.AckProgress)
|
|
nc.Flush()
|
|
|
|
// Now let's take longer than ackWait to process but signal we are working on the message.
|
|
timeout := time.Now().Add(5 * ackWait)
|
|
for time.Now().Before(timeout) {
|
|
time.Sleep(ackWait / 4)
|
|
m.Respond(server.AckProgress)
|
|
}
|
|
|
|
// 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(server.AckProgress)
|
|
|
|
// Now should get 1 back then 2.
|
|
m = getMsg(1, 4)
|
|
m.Respond(nil)
|
|
|
|
getMsg(2, 5)
|
|
})
|
|
}
|
|
}
|
|
func TestJetStreamRedeliveryAfterServerRestart(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
if config := s.JetStreamConfig(); config != nil {
|
|
defer os.RemoveAll(config.StoreDir)
|
|
}
|
|
|
|
sendSubj := "MYQ"
|
|
mset, err := s.GlobalAccount().AddMsgSet(&server.MsgSetConfig{Name: sendSubj, Storage: server.FileStorage})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %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++ {
|
|
resp, _ := nc.Request(sendSubj, []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
stats := mset.Stats()
|
|
if stats.Msgs != uint64(toSend) {
|
|
t.Fatalf("Expected %d messages, got %d", toSend, stats.Msgs)
|
|
}
|
|
|
|
sub, _ := nc.SubscribeSync(nats.NewInbox())
|
|
defer sub.Unsubscribe()
|
|
nc.Flush()
|
|
|
|
o, err := mset.AddObservable(&server.ObservableConfig{
|
|
Durable: "TO",
|
|
Delivery: sub.Subject,
|
|
DeliverAll: true,
|
|
AckPolicy: server.AckExplicit,
|
|
AckWait: 100 * time.Millisecond,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", 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
|
|
})
|
|
|
|
// Stop current server.
|
|
s.Shutdown()
|
|
|
|
// Restart.
|
|
s = RunBasicJetStreamServer()
|
|
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 TestJetStreamActiveDelivery(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "ADS", Storage: server.MemoryStorage, Subjects: []string{"foo.*"}}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "ADS", Storage: server.FileStorage, Subjects: []string{"foo.*"}}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
if config := s.JetStreamConfig(); config != nil && config.StoreDir != "" {
|
|
defer os.RemoveAll(config.StoreDir)
|
|
}
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %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++ {
|
|
resp, _ := nc.Request(sendSubj, []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
stats := mset.Stats()
|
|
if stats.Msgs != uint64(toSend) {
|
|
t.Fatalf("Expected %d messages, got %d", toSend, stats.Msgs)
|
|
}
|
|
|
|
o, err := mset.AddObservable(&server.ObservableConfig{Durable: "to", Delivery: "d", DeliverAll: true})
|
|
if err != nil {
|
|
t.Fatalf("Expected no error with registered interest, got %v", err)
|
|
}
|
|
defer o.Delete()
|
|
|
|
// We have no active interest above. So observable 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 TestJetStreamEphemeralObservables(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "EP", Storage: server.MemoryStorage, Subjects: []string{"foo.*"}}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "EP", Storage: server.FileStorage, Subjects: []string{"foo.*"}}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %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.AddObservable(&server.ObservableConfig{Delivery: sub.Subject})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if !o.Active() {
|
|
t.Fatalf("Expected the observable to be considered active")
|
|
}
|
|
if numo := mset.NumObservables(); numo != 1 {
|
|
t.Fatalf("Expected number of observables 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 observable.
|
|
sub.Unsubscribe()
|
|
checkFor(t, time.Second, 10*time.Millisecond, func() error {
|
|
if o.Active() {
|
|
return fmt.Errorf("Expected the ephemeral observable 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 observables.
|
|
if numo := mset.NumObservables(); numo != 1 {
|
|
t.Fatalf("Expected number of observables 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.NumObservables(); numo != 0 {
|
|
return fmt.Errorf("Expected number of observables to be 0, got %d", numo)
|
|
}
|
|
return nil
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamObservableReconnect(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "ET", Storage: server.MemoryStorage, Subjects: []string{"foo.*"}}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "ET", Storage: server.FileStorage, Subjects: []string{"foo.*"}}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
sub, _ := nc.SubscribeSync(nats.NewInbox())
|
|
defer sub.Unsubscribe()
|
|
nc.Flush()
|
|
|
|
// Capture the subscription.
|
|
delivery := sub.Subject
|
|
|
|
o, err := mset.AddObservable(&server.ObservableConfig{Delivery: delivery, AckPolicy: server.AckExplicit})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if !o.Active() {
|
|
t.Fatalf("Expected the observable to be considered active")
|
|
}
|
|
if numo := mset.NumObservables(); numo != 1 {
|
|
t.Fatalf("Expected number of observables to be 1, got %d", numo)
|
|
}
|
|
|
|
// We will simulate reconnect by unsubscribing on one connection and forming
|
|
// the same on another. Once we have cluster tests we will do more testing on
|
|
// reconnect scenarios.
|
|
getMsg := func(seqno int) *nats.Msg {
|
|
t.Helper()
|
|
m, err := sub.NextMsg(time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error for %d: %v", seqno, err)
|
|
}
|
|
if seq := o.SeqFromReply(m.Reply); seq != uint64(seqno) {
|
|
t.Fatalf("Expected sequence of %d , got %d", seqno, seq)
|
|
}
|
|
m.Respond(nil)
|
|
return m
|
|
}
|
|
|
|
sendMsg := func() {
|
|
t.Helper()
|
|
if err := nc.Publish("foo.22", []byte("OK!")); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
checkForInActive := func() {
|
|
checkFor(t, 250*time.Millisecond, 50*time.Millisecond, func() error {
|
|
if o.Active() {
|
|
return fmt.Errorf("Observable is still active")
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Send and Pull first message.
|
|
sendMsg() // 1
|
|
getMsg(1)
|
|
// Cancel first one.
|
|
sub.Unsubscribe()
|
|
// Re-establish new sub on same subject.
|
|
sub, _ = nc.SubscribeSync(delivery)
|
|
|
|
// We should be getting 2 here.
|
|
sendMsg() // 2
|
|
getMsg(2)
|
|
|
|
sub.Unsubscribe()
|
|
checkForInActive()
|
|
|
|
// send 3-10
|
|
for i := 0; i <= 7; i++ {
|
|
sendMsg()
|
|
}
|
|
// Make sure they are all queued up with no interest.
|
|
nc.Flush()
|
|
|
|
// Restablish again.
|
|
sub, _ = nc.SubscribeSync(delivery)
|
|
nc.Flush()
|
|
|
|
// We should be getting 3-10 here.
|
|
for i := 3; i <= 10; i++ {
|
|
getMsg(i)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamDurableObservableReconnect(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "DT", Storage: server.MemoryStorage, Subjects: []string{"foo.*"}}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "DT", Storage: server.FileStorage, Subjects: []string{"foo.*"}}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
dname := "d22"
|
|
subj1 := nats.NewInbox()
|
|
|
|
o, err := mset.AddObservable(&server.ObservableConfig{Durable: dname, Delivery: subj1, AckPolicy: server.AckExplicit})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
sendMsg := func() {
|
|
t.Helper()
|
|
if err := nc.Publish("foo.22", []byte("OK!")); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
// Send 10 msgs
|
|
toSend := 10
|
|
for i := 0; i < toSend; i++ {
|
|
sendMsg()
|
|
}
|
|
|
|
sub, _ := nc.SubscribeSync(subj1)
|
|
defer sub.Unsubscribe()
|
|
|
|
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
|
|
})
|
|
|
|
getMsg := func(seqno int) *nats.Msg {
|
|
t.Helper()
|
|
m, err := sub.NextMsg(time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if seq := o.SeqFromReply(m.Reply); seq != uint64(seqno) {
|
|
t.Fatalf("Expected sequence of %d , got %d", seqno, seq)
|
|
}
|
|
m.Respond(nil)
|
|
return m
|
|
}
|
|
|
|
// Ack first half
|
|
for i := 1; i <= toSend/2; i++ {
|
|
m := getMsg(i)
|
|
m.Respond(nil)
|
|
}
|
|
|
|
// We should not be able to try to add an observer with the same name.
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{Durable: dname, Delivery: subj1, AckPolicy: server.AckExplicit}); err == nil {
|
|
t.Fatalf("Expected and error trying to add a new durable observable while first still active")
|
|
}
|
|
|
|
// Now unsubscribe and wait to become inactive
|
|
sub.Unsubscribe()
|
|
checkFor(t, 250*time.Millisecond, 50*time.Millisecond, func() error {
|
|
if o.Active() {
|
|
return fmt.Errorf("Observable is still active")
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// Now we should be able to replace the delivery subject.
|
|
subj2 := nats.NewInbox()
|
|
sub, _ = nc.SubscribeSync(subj2)
|
|
defer sub.Unsubscribe()
|
|
nc.Flush()
|
|
|
|
o, err = mset.AddObservable(&server.ObservableConfig{Durable: dname, Delivery: subj2, AckPolicy: server.AckExplicit})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error trying to add a new durable observable: %v", err)
|
|
}
|
|
|
|
// We should get the remaining messages here.
|
|
for i := toSend / 2; i <= toSend; i++ {
|
|
m := getMsg(i)
|
|
m.Respond(nil)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamDurableSubjectedObservableReconnect(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "DT", Storage: server.MemoryStorage, Subjects: []string{"foo.*"}}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "DT", Storage: server.FileStorage, Subjects: []string{"foo.*"}}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
sendMsgs := func(toSend int) {
|
|
for i := 0; i < toSend; i++ {
|
|
var subj string
|
|
if i%2 == 0 {
|
|
subj = "foo.AA"
|
|
} else {
|
|
subj = "foo.ZZ"
|
|
}
|
|
if err := nc.Publish(subj, []byte("OK!")); err != nil {
|
|
return
|
|
}
|
|
}
|
|
nc.Flush()
|
|
}
|
|
|
|
// Send 50 msgs
|
|
toSend := 50
|
|
sendMsgs(toSend)
|
|
|
|
dname := "d33"
|
|
dsubj := nats.NewInbox()
|
|
|
|
// Now create an observable for foo.AA, only requesting the last one.
|
|
o, err := mset.AddObservable(&server.ObservableConfig{
|
|
Durable: dname,
|
|
Delivery: dsubj,
|
|
Subject: "foo.AA",
|
|
DeliverLast: true,
|
|
AckPolicy: server.AckExplicit,
|
|
AckWait: 100 * time.Millisecond,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
sub, _ := nc.SubscribeSync(dsubj)
|
|
defer sub.Unsubscribe()
|
|
|
|
// Used to calculate difference between store seq and delivery seq.
|
|
storeBaseOff := 47
|
|
|
|
getMsg := func(seq int) *nats.Msg {
|
|
t.Helper()
|
|
sseq := 2*seq + storeBaseOff
|
|
m, err := sub.NextMsg(time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
rsseq, roseq, dcount := o.ReplyInfo(m.Reply)
|
|
if roseq != uint64(seq) {
|
|
t.Fatalf("Expected observable sequence of %d , got %d", seq, roseq)
|
|
}
|
|
if rsseq != uint64(sseq) {
|
|
t.Fatalf("Expected msgset sequence of %d , got %d", sseq, rsseq)
|
|
}
|
|
if dcount != 1 {
|
|
t.Fatalf("Expected message to not be marked as redelivered")
|
|
}
|
|
return m
|
|
}
|
|
|
|
getRedeliveredMsg := func(seq int) *nats.Msg {
|
|
t.Helper()
|
|
m, err := sub.NextMsg(time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
_, roseq, dcount := o.ReplyInfo(m.Reply)
|
|
if roseq != uint64(seq) {
|
|
t.Fatalf("Expected observable sequence of %d , got %d", seq, roseq)
|
|
}
|
|
if dcount < 2 {
|
|
t.Fatalf("Expected message to be marked as redelivered")
|
|
}
|
|
// Ack this message.
|
|
m.Respond(nil)
|
|
return m
|
|
}
|
|
|
|
// All observables start at 1 and always have increasing sequence numbers.
|
|
m := getMsg(1)
|
|
m.Respond(nil)
|
|
|
|
// Now send 50 more, so 100 total, 26 (last + 50/2) for this observable.
|
|
sendMsgs(toSend)
|
|
|
|
stats := mset.Stats()
|
|
if stats.Msgs != uint64(toSend*2) {
|
|
t.Fatalf("Expected %d messages, got %d", toSend*2, stats.Msgs)
|
|
}
|
|
|
|
// For tracking next expected.
|
|
nextSeq := 2
|
|
noAcks := 0
|
|
for i := 0; i < toSend/2; i++ {
|
|
m := getMsg(nextSeq)
|
|
if i%2 == 0 {
|
|
m.Respond(nil) // Ack evens.
|
|
} else {
|
|
noAcks++
|
|
}
|
|
nextSeq++
|
|
}
|
|
|
|
// We should now get those redelivered.
|
|
for i := 0; i < noAcks; i++ {
|
|
getRedeliveredMsg(nextSeq)
|
|
nextSeq++
|
|
}
|
|
|
|
// Now send 50 more.
|
|
sendMsgs(toSend)
|
|
|
|
storeBaseOff -= noAcks * 2
|
|
|
|
for i := 0; i < toSend/2; i++ {
|
|
m := getMsg(nextSeq)
|
|
m.Respond(nil)
|
|
nextSeq++
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamRedeliverCount(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "DC", Storage: server.MemoryStorage}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "DC", Storage: server.FileStorage}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
// Send 10 msgs
|
|
for i := 0; i < 10; i++ {
|
|
nc.Publish("DC", []byte("OK!"))
|
|
}
|
|
nc.Flush()
|
|
if stats := mset.Stats(); stats.Msgs != 10 {
|
|
t.Fatalf("Expected %d messages, got %d", 10, stats.Msgs)
|
|
}
|
|
|
|
o, err := mset.AddObservable(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 := o.ReplyInfo(m.Reply)
|
|
// Make sure we keep getting message set 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(server.AckNak)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamCanNotNakAckd(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "DC", Storage: server.MemoryStorage}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "DC", Storage: server.FileStorage}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
// Send 10 msgs
|
|
for i := 0; i < 10; i++ {
|
|
nc.Publish("DC", []byte("OK!"))
|
|
}
|
|
nc.Flush()
|
|
if stats := mset.Stats(); stats.Msgs != 10 {
|
|
t.Fatalf("Expected %d messages, got %d", 10, stats.Msgs)
|
|
}
|
|
|
|
o, err := mset.AddObservable(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), server.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 TestJetStreamMsgSetPurge(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "DC", Storage: server.MemoryStorage}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "DC", Storage: server.FileStorage}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
// Send 100 msgs
|
|
for i := 0; i < 100; i++ {
|
|
nc.Publish("DC", []byte("OK!"))
|
|
}
|
|
nc.Flush()
|
|
if stats := mset.Stats(); stats.Msgs != 100 {
|
|
t.Fatalf("Expected %d messages, got %d", 100, stats.Msgs)
|
|
}
|
|
mset.Purge()
|
|
if stats := mset.Stats(); stats.Msgs != 0 {
|
|
t.Fatalf("Expected %d messages, got %d", 0, stats.Msgs)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamMsgSetPurgeWithObservable(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "DC", Storage: server.MemoryStorage}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "DC", Storage: server.FileStorage}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
// Send 100 msgs
|
|
for i := 0; i < 100; i++ {
|
|
nc.Publish("DC", []byte("OK!"))
|
|
}
|
|
nc.Flush()
|
|
if stats := mset.Stats(); stats.Msgs != 100 {
|
|
t.Fatalf("Expected %d messages, got %d", 100, stats.Msgs)
|
|
}
|
|
// Now create an observable and make sure it functions properly.
|
|
o, err := mset.AddObservable(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().State
|
|
if state.AckFloor.ObsSeq != 50 {
|
|
t.Fatalf("Expected ack floor of 50, got %d", state.AckFloor.ObsSeq)
|
|
}
|
|
if len(state.Pending) != 25 {
|
|
t.Fatalf("Expected len(pending) to be 25, got %d", len(state.Pending))
|
|
}
|
|
// Now do purge.
|
|
mset.Purge()
|
|
if stats := mset.Stats(); stats.Msgs != 0 {
|
|
t.Fatalf("Expected %d messages, got %d", 0, stats.Msgs)
|
|
}
|
|
// Now re-acquire state and check that we did the right thing.
|
|
// Pending should be cleared, and msgset sequences should have been set
|
|
// to the total messages before purge + 1.
|
|
state = o.Info().State
|
|
if len(state.Pending) != 0 {
|
|
t.Fatalf("Expected no pending, got %d", len(state.Pending))
|
|
}
|
|
if state.Delivered.SetSeq != 101 {
|
|
t.Fatalf("Expected to have setseq now at next seq of 101, got %d", state.Delivered.SetSeq)
|
|
}
|
|
// Check AckFloors which should have also been adjusted.
|
|
if state.AckFloor.SetSeq != 100 {
|
|
t.Fatalf("Expected ackfloor for setseq to be 100, got %d", state.AckFloor.SetSeq)
|
|
}
|
|
if state.AckFloor.ObsSeq != 75 {
|
|
t.Fatalf("Expected ackfloor for obsseq to be 75, got %d", state.AckFloor.ObsSeq)
|
|
}
|
|
// Also make sure we can get new messages correctly.
|
|
nc.Request("DC", []byte("OK-22"), time.Second)
|
|
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 TestJetStreamInterestRetentionMsgSet(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "DC", Storage: server.MemoryStorage, Retention: server.InterestPolicy}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "DC", Storage: server.FileStorage, Retention: server.InterestPolicy}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
// Send 100 msgs
|
|
totalMsgs := 100
|
|
|
|
for i := 0; i < totalMsgs; i++ {
|
|
nc.Publish("DC", []byte("OK!"))
|
|
}
|
|
nc.Flush()
|
|
|
|
checkNumMsgs := func(numExpected int) {
|
|
t.Helper()
|
|
if stats := mset.Stats(); stats.Msgs != uint64(numExpected) {
|
|
t.Fatalf("Expected %d messages, got %d", numExpected, stats.Msgs)
|
|
}
|
|
}
|
|
|
|
checkNumMsgs(totalMsgs)
|
|
|
|
syncSub := func() *nats.Subscription {
|
|
sub, _ := nc.SubscribeSync(nats.NewInbox())
|
|
nc.Flush()
|
|
return sub
|
|
}
|
|
|
|
// Now create three observables.
|
|
// 1. AckExplicit
|
|
// 2. AckAll
|
|
// 3. AckNone
|
|
|
|
sub1 := syncSub()
|
|
mset.AddObservable(&server.ObservableConfig{Delivery: sub1.Subject, DeliverAll: true, AckPolicy: server.AckExplicit})
|
|
|
|
sub2 := syncSub()
|
|
mset.AddObservable(&server.ObservableConfig{Delivery: sub2.Subject, DeliverAll: true, AckPolicy: server.AckAll})
|
|
|
|
sub3 := syncSub()
|
|
mset.AddObservable(&server.ObservableConfig{Delivery: sub3.Subject, DeliverAll: true, AckPolicy: server.AckNone})
|
|
|
|
// Wait for all messsages to be pending for each sub.
|
|
for _, sub := range []*nats.Subscription{sub1, sub2, sub3} {
|
|
checkFor(t, 250*time.Millisecond, 10*time.Millisecond, func() error {
|
|
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != totalMsgs {
|
|
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, totalMsgs)
|
|
}
|
|
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)
|
|
|
|
// Now ack last ackall message. This should clear all of them.
|
|
for i := 4; i <= totalMsgs; i++ {
|
|
if m, err := sub2.NextMsg(time.Second); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
} else if i == totalMsgs {
|
|
m.Respond(nil)
|
|
}
|
|
}
|
|
nc.Flush()
|
|
|
|
// Should be zero now.
|
|
checkNumMsgs(0)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamObservableReplayRate(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "DC", Storage: server.MemoryStorage, Retention: server.InterestPolicy}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "DC", Storage: server.FileStorage, Retention: server.InterestPolicy}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
// Send 10 msgs
|
|
totalMsgs := 10
|
|
|
|
var gaps []time.Duration
|
|
lst := time.Now()
|
|
|
|
for i := 0; i < totalMsgs; i++ {
|
|
gaps = append(gaps, time.Since(lst))
|
|
nc.Request("DC", []byte("OK!"), time.Second)
|
|
lst = time.Now()
|
|
// Calculate a gap between messages.
|
|
gap := 10*time.Millisecond + time.Duration(rand.Intn(20))*time.Millisecond
|
|
time.Sleep(gap)
|
|
}
|
|
|
|
if stats := mset.Stats(); stats.Msgs != uint64(totalMsgs) {
|
|
t.Fatalf("Expected %d messages, got %d", totalMsgs, stats.Msgs)
|
|
}
|
|
|
|
sub, _ := nc.SubscribeSync(nats.NewInbox())
|
|
defer sub.Unsubscribe()
|
|
nc.Flush()
|
|
|
|
o, err := mset.AddObservable(&server.ObservableConfig{Delivery: sub.Subject, DeliverAll: true})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer o.Delete()
|
|
|
|
// Firehose/instant which is default.
|
|
last := time.Now()
|
|
for i := 0; i < totalMsgs; i++ {
|
|
if _, err := sub.NextMsg(time.Second); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
now := time.Now()
|
|
if now.Sub(last) > 5*time.Millisecond {
|
|
t.Fatalf("Expected firehose/instant delivery, got message gap of %v", now.Sub(last))
|
|
}
|
|
last = now
|
|
}
|
|
|
|
// Now do replay rate to match original.
|
|
o, err = mset.AddObservable(&server.ObservableConfig{Delivery: sub.Subject, DeliverAll: true, ReplayPolicy: server.ReplayOriginal})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer o.Delete()
|
|
|
|
// Original rate messsages were received for push based observable.
|
|
for i := 0; i < totalMsgs; i++ {
|
|
start := time.Now()
|
|
if _, err := sub.NextMsg(time.Second); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
gap := time.Since(start)
|
|
// 10ms is high but on macs time.Sleep(delay) does not sleep only delay.
|
|
if gap < gaps[i] || gap > gaps[i]+10*time.Millisecond {
|
|
t.Fatalf("Gap is incorrect for %d, expected %v got %v", i, gaps[i], gap)
|
|
}
|
|
}
|
|
|
|
// Now create pull based.
|
|
oc := workerModeConfig("PM")
|
|
oc.ReplayPolicy = server.ReplayOriginal
|
|
o, err = mset.AddObservable(oc)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer o.Delete()
|
|
|
|
for i := 0; i < totalMsgs; i++ {
|
|
start := time.Now()
|
|
if _, err := nc.Request(o.RequestNextMsgSubject(), nil, time.Second); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
gap := time.Since(start)
|
|
// 10ms is high but on macs time.Sleep(delay) does not sleep only delay.
|
|
if gap < gaps[i] || gap > gaps[i]+10*time.Millisecond {
|
|
t.Fatalf("Gap is incorrect for %d, expected %v got %v", i, gaps[i], gap)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamObservableReplayRateNoAck(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "DC", Storage: server.MemoryStorage}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "DC", Storage: server.FileStorage}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
// Send 10 msgs
|
|
totalMsgs := 10
|
|
for i := 0; i < totalMsgs; i++ {
|
|
nc.Request("DC", []byte("Hello World"), time.Second)
|
|
time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)
|
|
}
|
|
if stats := mset.Stats(); stats.Msgs != uint64(totalMsgs) {
|
|
t.Fatalf("Expected %d messages, got %d", totalMsgs, stats.Msgs)
|
|
}
|
|
subj := "d.dc"
|
|
o, err := mset.AddObservable(&server.ObservableConfig{
|
|
Durable: "derek",
|
|
Delivery: subj,
|
|
DeliverAll: true,
|
|
AckPolicy: server.AckNone,
|
|
ReplayPolicy: server.ReplayOriginal,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
defer o.Delete()
|
|
// Sleep a random amount of time.
|
|
time.Sleep(time.Duration(rand.Intn(20)) * time.Millisecond)
|
|
|
|
sub, _ := nc.SubscribeSync(subj)
|
|
nc.Flush()
|
|
|
|
checkFor(t, time.Second, 25*time.Millisecond, func() error {
|
|
if nmsgs, _, _ := sub.Pending(); err != nil || nmsgs != totalMsgs {
|
|
return fmt.Errorf("Did not receive correct number of messages: %d vs %d", nmsgs, totalMsgs)
|
|
}
|
|
return nil
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamObservableReplayQuit(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{"MemoryStore", &server.MsgSetConfig{Name: "DC", Storage: server.MemoryStorage, Retention: server.InterestPolicy}},
|
|
{"FileStore", &server.MsgSetConfig{Name: "DC", Storage: server.FileStorage, Retention: server.InterestPolicy}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err := s.GlobalAccount().AddMsgSet(c.mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
// Send 2 msgs
|
|
nc.Request("DC", []byte("OK!"), time.Second)
|
|
time.Sleep(100 * time.Millisecond)
|
|
nc.Request("DC", []byte("OK!"), time.Second)
|
|
|
|
if stats := mset.Stats(); stats.Msgs != 2 {
|
|
t.Fatalf("Expected %d messages, got %d", 2, stats.Msgs)
|
|
}
|
|
|
|
sub, _ := nc.SubscribeSync(nats.NewInbox())
|
|
defer sub.Unsubscribe()
|
|
nc.Flush()
|
|
|
|
// Now do replay rate to match original.
|
|
o, err := mset.AddObservable(&server.ObservableConfig{Delivery: sub.Subject, DeliverAll: true, ReplayPolicy: server.ReplayOriginal})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Allow loop and deliver / replay go routine to spin up
|
|
time.Sleep(50 * time.Millisecond)
|
|
base := runtime.NumGoroutine()
|
|
o.Delete()
|
|
|
|
checkFor(t, 100*time.Millisecond, 10*time.Millisecond, func() error {
|
|
if runtime.NumGoroutine() >= base {
|
|
return fmt.Errorf("Observable go routines still running")
|
|
}
|
|
return nil
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamSystemLimits(t *testing.T) {
|
|
s := RunRandClientPortServer()
|
|
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 := &server.JetStreamConfig{MaxMemory: 1024, MaxStore: 8192}
|
|
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) *server.JetStreamAccountLimits {
|
|
return &server.JetStreamAccountLimits{
|
|
MaxMemory: mem,
|
|
MaxStore: store,
|
|
MaxMsgSets: -1,
|
|
MaxObservables: -1,
|
|
}
|
|
}
|
|
|
|
if err := facc.EnableJetStream(limits(24, 192)); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
// Use up rest of our resources in memory
|
|
if err := bacc.EnableJetStream(limits(1000, 0)); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Now ask for more memory. Should error.
|
|
if err := zacc.EnableJetStream(limits(1000, 0)); err == nil {
|
|
t.Fatalf("Expected an error when exhausting memory resource limits")
|
|
}
|
|
// Disk too.
|
|
if err := zacc.EnableJetStream(limits(0, 10000)); 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", server.FriendlyBytes(rm), server.FriendlyBytes(rd))
|
|
}
|
|
|
|
if err := facc.EnableJetStream(limits(24, 192)); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
// Test Adjust
|
|
l := limits(jsconfig.MaxMemory, jsconfig.MaxStore)
|
|
l.MaxMsgSets = 10
|
|
l.MaxObservables = 10
|
|
|
|
if err := facc.UpdateJetStreamLimits(l); err != nil {
|
|
t.Fatalf("Unexpected error updating jetstream account limits: %v", err)
|
|
}
|
|
|
|
var msets []*server.MsgSet
|
|
// Now test max message sets and max observables. Note max observables is per message set.
|
|
for i := 0; i < 10; i++ {
|
|
mname := fmt.Sprintf("foo.%d", i)
|
|
mset, err := facc.AddMsgSet(&server.MsgSetConfig{Name: strconv.Itoa(i), Subjects: []string{mname}})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
msets = append(msets, mset)
|
|
}
|
|
|
|
// This one should fail since over the limit for max number of message sets.
|
|
if _, err := facc.AddMsgSet(&server.MsgSetConfig{Name: "22", Subjects: []string{"foo.22"}}); err == nil {
|
|
t.Fatalf("Expected error adding message set over limit")
|
|
}
|
|
|
|
// 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.AddMsgSet(&server.MsgSetConfig{Name: "22", MaxBytes: jsconfig.MaxMemory * 2}); err == nil {
|
|
t.Fatalf("Expected error adding message set over limit")
|
|
}
|
|
|
|
// Replicas can't be > 1
|
|
if _, err := facc.AddMsgSet(&server.MsgSetConfig{Name: "22", Replicas: 10}); err == nil {
|
|
t.Fatalf("Expected error adding message set over limit")
|
|
}
|
|
|
|
// Test observables limit
|
|
mset, err := facc.AddMsgSet(&server.MsgSetConfig{Name: "22", Subjects: []string{"foo.22"}})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
|
|
for i := 0; i < 10; i++ {
|
|
oname := fmt.Sprintf("O:%d", i)
|
|
_, err := mset.AddObservable(&server.ObservableConfig{Durable: oname, AckPolicy: server.AckExplicit})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
// This one should fail.
|
|
if _, err := mset.AddObservable(&server.ObservableConfig{Durable: "O:22", AckPolicy: server.AckExplicit}); err == nil {
|
|
t.Fatalf("Expected error adding observable over the limit")
|
|
}
|
|
}
|
|
|
|
func TestJetStreamMsgSetStorageTrackingAndLimits(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
gacc := s.GlobalAccount()
|
|
|
|
al := &server.JetStreamAccountLimits{
|
|
MaxMemory: 8192,
|
|
MaxStore: -1,
|
|
MaxMsgSets: -1,
|
|
MaxObservables: -1,
|
|
}
|
|
|
|
if err := gacc.UpdateJetStreamLimits(al); err != nil {
|
|
t.Fatalf("Unexpected error updating jetstream account limits: %v", err)
|
|
}
|
|
|
|
mset, err := gacc.AddMsgSet(&server.MsgSetConfig{Name: "LIMITS", Retention: server.WorkQueuePolicy})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
toSend := 100
|
|
for i := 0; i < toSend; i++ {
|
|
resp, _ := nc.Request("LIMITS", []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
|
|
stats := mset.Stats()
|
|
usage := gacc.JetStreamUsage()
|
|
|
|
// Make sure these are working correctly.
|
|
if stats.Bytes != usage.Memory {
|
|
t.Fatalf("Expected to have message set bytes match memory usage, %d vs %d", stats.Bytes, usage.Memory)
|
|
}
|
|
if usage.MsgSets != 1 {
|
|
t.Fatalf("Expected to have 1 msgset, got %d", usage.MsgSets)
|
|
}
|
|
|
|
// Do second msgset.
|
|
mset2, err := gacc.AddMsgSet(&server.MsgSetConfig{Name: "NUM22", Retention: server.WorkQueuePolicy})
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset2.Delete()
|
|
|
|
for i := 0; i < toSend; i++ {
|
|
resp, _ := nc.Request("NUM22", []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
|
|
stats2 := mset2.Stats()
|
|
usage = gacc.JetStreamUsage()
|
|
|
|
if usage.Memory != (stats.Bytes + stats2.Bytes) {
|
|
t.Fatalf("Expected to track both msgsets, account is %v, msgset1 is %v, msgset2 is %v", usage.Memory, stats.Bytes, stats2.Bytes)
|
|
}
|
|
|
|
// Make sure delete works.
|
|
mset2.Delete()
|
|
stats2 = mset2.Stats()
|
|
usage = gacc.JetStreamUsage()
|
|
|
|
if usage.Memory != (stats.Bytes + stats2.Bytes) {
|
|
t.Fatalf("Expected to track both msgsets, account is %v, msgset1 is %v, msgset2 is %v", usage.Memory, stats.Bytes, stats2.Bytes)
|
|
}
|
|
|
|
// Now drain the first one by consuming the messages.
|
|
o, err := mset.AddObservable(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()
|
|
|
|
stats = mset.Stats()
|
|
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) != server.OK {
|
|
errSeen = string(resp.Data)
|
|
break
|
|
}
|
|
}
|
|
|
|
if errSeen == "" {
|
|
t.Fatalf("Expected to see an error when exceeding the account limits")
|
|
}
|
|
|
|
stats = mset.Stats()
|
|
usage = gacc.JetStreamUsage()
|
|
|
|
if usage.Memory > uint64(al.MaxMemory) {
|
|
t.Fatalf("Expected memory to not exceed limit of %d, got %d", al.MaxMemory, usage.Memory)
|
|
}
|
|
}
|
|
|
|
func TestJetStreamMsgSetFileStorageTrackingAndLimits(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
gacc := s.GlobalAccount()
|
|
|
|
al := &server.JetStreamAccountLimits{
|
|
MaxMemory: 8192,
|
|
MaxStore: 9600,
|
|
MaxMsgSets: -1,
|
|
MaxObservables: -1,
|
|
}
|
|
|
|
if err := gacc.UpdateJetStreamLimits(al); err != nil {
|
|
t.Fatalf("Unexpected error updating jetstream account limits: %v", err)
|
|
}
|
|
|
|
mconfig := &server.MsgSetConfig{Name: "LIMITS", Storage: server.FileStorage, Retention: server.WorkQueuePolicy}
|
|
mset, err := gacc.AddMsgSet(mconfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
toSend := 100
|
|
for i := 0; i < toSend; i++ {
|
|
resp, _ := nc.Request("LIMITS", []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
|
|
stats := mset.Stats()
|
|
usage := gacc.JetStreamUsage()
|
|
|
|
// Make sure these are working correctly.
|
|
if usage.Store != stats.Bytes {
|
|
t.Fatalf("Expected to have message set bytes match the store usage, %d vs %d", usage.Store, stats.Bytes)
|
|
}
|
|
if usage.MsgSets != 1 {
|
|
t.Fatalf("Expected to have 1 msgset, got %d", usage.MsgSets)
|
|
}
|
|
|
|
// Do second msgset.
|
|
mconfig2 := &server.MsgSetConfig{Name: "NUM22", Storage: server.FileStorage, Retention: server.WorkQueuePolicy}
|
|
mset2, err := gacc.AddMsgSet(mconfig2)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset2.Delete()
|
|
|
|
for i := 0; i < toSend; i++ {
|
|
resp, _ := nc.Request("NUM22", []byte("Hello World!"), 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
|
|
stats2 := mset2.Stats()
|
|
usage = gacc.JetStreamUsage()
|
|
|
|
if usage.Store != (stats.Bytes + stats2.Bytes) {
|
|
t.Fatalf("Expected to track both msgsets, usage is %v, msgset1 is %v, msgset2 is %v", usage.Store, stats.Bytes, stats2.Bytes)
|
|
}
|
|
|
|
// Make sure delete works.
|
|
mset2.Delete()
|
|
stats2 = mset2.Stats()
|
|
usage = gacc.JetStreamUsage()
|
|
|
|
if usage.Store != (stats.Bytes + stats2.Bytes) {
|
|
t.Fatalf("Expected to track both msgsets, account is %v, msgset1 is %v, msgset2 is %v", usage.Store, stats.Bytes, stats2.Bytes)
|
|
}
|
|
|
|
// Now drain the first one by consuming the messages.
|
|
o, err := mset.AddObservable(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()
|
|
|
|
stats = mset.Stats()
|
|
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) != server.OK {
|
|
errSeen = string(resp.Data)
|
|
break
|
|
}
|
|
}
|
|
|
|
if errSeen == "" {
|
|
t.Fatalf("Expected to see an error when exceeding the account limits")
|
|
}
|
|
|
|
stats = mset.Stats()
|
|
usage = gacc.JetStreamUsage()
|
|
|
|
if usage.Memory > uint64(al.MaxMemory) {
|
|
t.Fatalf("Expected memory to not exceed limit of %d, got %d", al.MaxMemory, usage.Memory)
|
|
}
|
|
}
|
|
|
|
func TestJetStreamSimpleFileStorageRecovery(t *testing.T) {
|
|
base := runtime.NumGoroutine()
|
|
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
config := s.JetStreamConfig()
|
|
if config == nil {
|
|
t.Fatalf("Expected non-nil config")
|
|
}
|
|
defer os.RemoveAll(config.StoreDir)
|
|
|
|
acc := s.GlobalAccount()
|
|
|
|
type obsi struct {
|
|
cfg server.ObservableConfig
|
|
ack int
|
|
}
|
|
type info struct {
|
|
cfg server.MsgSetConfig
|
|
stats server.MsgSetStats
|
|
obs []obsi
|
|
}
|
|
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()
|
|
|
|
numMsgSets := 10
|
|
for i := 1; i <= numMsgSets; i++ {
|
|
msetName := fmt.Sprintf("MMS-%d", i)
|
|
subjects := []string{randomSubject(), randomSubject(), randomSubject()}
|
|
msetConfig := server.MsgSetConfig{
|
|
Name: msetName,
|
|
Storage: server.FileStorage,
|
|
Subjects: subjects,
|
|
MaxMsgs: 100,
|
|
}
|
|
mset, err := acc.AddMsgSet(&msetConfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
defer mset.Delete()
|
|
|
|
toSend := rand.Intn(100) + 1
|
|
for n := 1; n <= toSend; n++ {
|
|
msg := []byte(fmt.Sprintf("Hello %d", n*i))
|
|
subj := subjects[rand.Intn(len(subjects))]
|
|
resp, _ := nc.Request(subj, msg, 50*time.Millisecond)
|
|
expectOKResponse(t, resp)
|
|
}
|
|
// Create up to 5 observables.
|
|
numObs := rand.Intn(5) + 1
|
|
var obs []obsi
|
|
for n := 1; n <= numObs; n++ {
|
|
oname := fmt.Sprintf("WQ-%d", n)
|
|
o, err := mset.AddObservable(workerModeConfig(oname))
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
// Now grab some messages.
|
|
toReceive := rand.Intn(toSend) + 1
|
|
for r := 0; r < toReceive; r++ {
|
|
resp, _ := nc.Request(o.RequestNextMsgSubject(), nil, time.Second)
|
|
if resp != nil {
|
|
resp.Respond(nil)
|
|
}
|
|
}
|
|
obs = append(obs, obsi{o.Config(), toReceive})
|
|
}
|
|
ostate[msetName] = info{mset.Config(), mset.Stats(), obs}
|
|
}
|
|
pusage := acc.JetStreamUsage()
|
|
|
|
// Shutdown the server. Restart and make sure things come back.
|
|
s.Shutdown()
|
|
time.Sleep(200 * time.Millisecond)
|
|
delta := (runtime.NumGoroutine() - base)
|
|
if delta > 3 {
|
|
fmt.Printf("%d Go routines still exist post Shutdown()", delta)
|
|
time.Sleep(30 * time.Second)
|
|
}
|
|
|
|
s = RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
acc = s.GlobalAccount()
|
|
|
|
nusage := acc.JetStreamUsage()
|
|
if nusage != pusage {
|
|
t.Fatalf("Usage does not match after restore: %+v vs %+v", nusage, pusage)
|
|
}
|
|
|
|
for mname, info := range ostate {
|
|
mset, err := acc.LookupMsgSet(mname)
|
|
if err != nil {
|
|
t.Fatalf("Expected to find a msgset for %q", mname)
|
|
}
|
|
if stats := mset.Stats(); stats != info.stats {
|
|
t.Fatalf("Stats do not match: %+v vs %+v", stats, info.stats)
|
|
}
|
|
if cfg := mset.Config(); !reflect.DeepEqual(cfg, info.cfg) {
|
|
t.Fatalf("Configs do not match: %+v vs %+v", cfg, info.cfg)
|
|
}
|
|
// Observables.
|
|
if mset.NumObservables() != len(info.obs) {
|
|
t.Fatalf("Number of observables do not match: %d vs %d", mset.NumObservables(), len(info.obs))
|
|
}
|
|
for _, oi := range info.obs {
|
|
if o := mset.LookupObservable(oi.cfg.Durable); o != nil {
|
|
if uint64(oi.ack+1) != o.NextSeq() {
|
|
t.Fatalf("Observable next seq is not correct: %d vs %d", oi.ack+1, o.NextSeq())
|
|
}
|
|
} else {
|
|
t.Fatalf("Expected to get an observable")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestJetStreamRequestAPI(t *testing.T) {
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
// Forced cleanup of all persisted state.
|
|
config := s.JetStreamConfig()
|
|
if config == nil {
|
|
t.Fatalf("Expected non-nil config")
|
|
}
|
|
defer os.RemoveAll(config.StoreDir)
|
|
|
|
// Client for API requests.
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
// This will return +OK if enabled.
|
|
resp, _ := nc.Request(server.JetStreamEnabled, nil, time.Second)
|
|
expectOKResponse(t, resp)
|
|
|
|
// This will get the current information about usage and limits for this account.
|
|
resp, err := nc.Request(server.JetStreamInfo, nil, time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
var info server.JetStreamAccountStats
|
|
if err := json.Unmarshal(resp.Data, &info); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Now create a message set.
|
|
msetCfg := server.MsgSetConfig{
|
|
Name: "MSET22",
|
|
Storage: server.FileStorage,
|
|
Subjects: []string{"foo", "bar", "baz"},
|
|
MaxMsgs: 100,
|
|
}
|
|
req, err := json.Marshal(msetCfg)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
resp, _ = nc.Request(server.JetStreamCreateMsgSet, req, time.Second)
|
|
expectOKResponse(t, resp)
|
|
|
|
// Now lookup info again and see that we can see the new message set.
|
|
resp, err = nc.Request(server.JetStreamInfo, nil, time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err = json.Unmarshal(resp.Data, &info); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if info.MsgSets != 1 {
|
|
t.Fatalf("Expected to see 1 MsgSet, got %d", info.MsgSets)
|
|
}
|
|
|
|
// Make sure list works.
|
|
resp, err = nc.Request(server.JetStreamMsgSets, nil, time.Second)
|
|
var names []string
|
|
if err = json.Unmarshal(resp.Data, &names); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if len(names) != 1 {
|
|
t.Fatalf("Expected only 1 message set but got %d", len(names))
|
|
}
|
|
if names[0] != msetCfg.Name {
|
|
t.Fatalf("Expected to get %q, but got %q", msetCfg.Name, names[0])
|
|
}
|
|
|
|
// Now send some messages, then we can poll for info on this message set.
|
|
toSend := 10
|
|
for i := 0; i < toSend; i++ {
|
|
nc.Request("foo", []byte("WELCOME JETSTREAM"), time.Second)
|
|
}
|
|
|
|
resp, err = nc.Request(server.JetStreamMsgSetInfo, []byte(msetCfg.Name), time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
var msi server.MsgSetInfo
|
|
if err = json.Unmarshal(resp.Data, &msi); err != nil {
|
|
log.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if msi.Stats.Msgs != uint64(toSend) {
|
|
t.Fatalf("Expected to get %d msgs, got %d", toSend, msi.Stats.Msgs)
|
|
}
|
|
|
|
// Looking up on that is not there should yield an error.
|
|
resp, err = nc.Request(server.JetStreamMsgSetInfo, []byte("BOB"), time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if string(resp.Data) != "-ERR msgset not found" {
|
|
t.Fatalf("Expected to get a not found error, got %q", resp.Data)
|
|
}
|
|
|
|
// Now create an observable.
|
|
delivery := nats.NewInbox()
|
|
obsReq := server.CreateObservableRequest{
|
|
MsgSet: msetCfg.Name,
|
|
Config: server.ObservableConfig{Delivery: delivery, DeliverAll: true},
|
|
}
|
|
req, err = json.Marshal(obsReq)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
resp, _ = nc.Request(server.JetStreamCreateObservable, req, time.Second)
|
|
// Since we do not have interest this should have failed.
|
|
if !strings.HasPrefix(string(resp.Data), "-ERR observable requires interest") {
|
|
t.Fatalf("Got wrong error response: %q", resp.Data)
|
|
}
|
|
// Now create subscription and make sure we get +OK
|
|
sub, _ := nc.SubscribeSync(delivery)
|
|
nc.Flush()
|
|
|
|
resp, _ = nc.Request(server.JetStreamCreateObservable, req, time.Second)
|
|
if string(resp.Data) != server.OK {
|
|
t.Fatalf("Expected OK, got %q", resp.Data)
|
|
}
|
|
|
|
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
|
|
})
|
|
|
|
// Get the list of all of the obervables for our message set.
|
|
resp, _ = nc.Request(server.JetStreamObservables, []byte(msetCfg.Name), time.Second)
|
|
var onames []string
|
|
if err = json.Unmarshal(resp.Data, &onames); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if len(onames) != 1 {
|
|
t.Fatalf("Expected only 1 observable but got %d", len(onames))
|
|
}
|
|
// Now let's get info about our observable.
|
|
req = []byte(fmt.Sprintf("%s %s", msetCfg.Name, onames[0]))
|
|
resp, _ = nc.Request(server.JetStreamObservableInfo, req, time.Second)
|
|
var ostate server.ObservableState
|
|
if err = json.Unmarshal(resp.Data, &ostate); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Now delete the observable.
|
|
resp, _ = nc.Request(server.JetStreamDeleteObservable, req, time.Second)
|
|
expectOKResponse(t, resp)
|
|
|
|
// Now delete the message set
|
|
resp, _ = nc.Request(server.JetStreamDeleteMsgSet, []byte(msetCfg.Name), time.Second)
|
|
expectOKResponse(t, resp)
|
|
|
|
// Now grab stats again.
|
|
// This will get the current information about usage and limits for this account.
|
|
resp, err = nc.Request(server.JetStreamInfo, nil, time.Second)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if err := json.Unmarshal(resp.Data, &info); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if info.MsgSets != 0 {
|
|
t.Fatalf("Expected no remaining message sets, got %d", info.MsgSets)
|
|
}
|
|
}
|
|
|
|
func TestJetStreamDeleteMsg(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mconfig *server.MsgSetConfig
|
|
}{
|
|
{name: "MemoryStore",
|
|
mconfig: &server.MsgSetConfig{
|
|
Name: "foo",
|
|
Retention: server.StreamPolicy,
|
|
MaxAge: time.Hour,
|
|
Storage: server.MemoryStorage,
|
|
Replicas: 1,
|
|
}},
|
|
{name: "FileStore",
|
|
mconfig: &server.MsgSetConfig{
|
|
Name: "foo",
|
|
Retention: server.StreamPolicy,
|
|
MaxAge: time.Hour,
|
|
Storage: server.FileStorage,
|
|
Replicas: 1,
|
|
}},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
config := s.JetStreamConfig()
|
|
if config == nil {
|
|
t.Fatalf("Expected non-nil config")
|
|
}
|
|
defer os.RemoveAll(config.StoreDir)
|
|
|
|
cfg := &server.MsgSetConfig{Name: "foo", Storage: server.FileStorage}
|
|
mset, err := s.GlobalAccount().AddMsgSet(cfg)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
pubTen := func() {
|
|
t.Helper()
|
|
for i := 0; i < 10; i++ {
|
|
nc.Publish("foo", []byte("Hello World!"))
|
|
}
|
|
nc.Flush()
|
|
}
|
|
|
|
pubTen()
|
|
|
|
stats := mset.Stats()
|
|
if stats.Msgs != 10 {
|
|
t.Fatalf("Expected 10 messages, got %d", stats.Msgs)
|
|
}
|
|
bytesPerMsg := stats.Bytes / 10
|
|
if bytesPerMsg == 0 {
|
|
t.Fatalf("Expected non-zero bytes for msg size")
|
|
}
|
|
|
|
deleteAndCheck := func(seq, expectedFirstSeq uint64) {
|
|
t.Helper()
|
|
beforeStats := mset.Stats()
|
|
if !mset.DeleteMsg(seq) {
|
|
t.Fatalf("Expected the delete of sequence %d to succeed", seq)
|
|
}
|
|
expectedStats := beforeStats
|
|
expectedStats.Msgs--
|
|
expectedStats.Bytes -= bytesPerMsg
|
|
expectedStats.FirstSeq = expectedFirstSeq
|
|
afterStats := mset.Stats()
|
|
if afterStats != expectedStats {
|
|
t.Fatalf("Stats not what we expected. Expected %+v, got %+v\n", expectedStats, afterStats)
|
|
}
|
|
}
|
|
|
|
// Delete one from the middle
|
|
deleteAndCheck(5, 1)
|
|
// Now make sure sequences are update properly.
|
|
// Delete first msg.
|
|
deleteAndCheck(1, 2)
|
|
// Now last
|
|
deleteAndCheck(10, 2)
|
|
// Now gaps.
|
|
deleteAndCheck(3, 2)
|
|
deleteAndCheck(2, 4)
|
|
|
|
mset.Purge()
|
|
// Put ten more one.
|
|
pubTen()
|
|
deleteAndCheck(11, 12)
|
|
deleteAndCheck(15, 12)
|
|
deleteAndCheck(16, 12)
|
|
deleteAndCheck(20, 12)
|
|
|
|
// Shutdown the server.
|
|
s.Shutdown()
|
|
|
|
s = RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
mset, err = s.GlobalAccount().LookupMsgSet("foo")
|
|
if err != nil {
|
|
t.Fatalf("Expected to get the message set back")
|
|
}
|
|
|
|
expected := server.MsgSetStats{Msgs: 6, Bytes: 6 * bytesPerMsg, FirstSeq: 12, LastSeq: 20}
|
|
stats = mset.Stats()
|
|
if stats != expected {
|
|
t.Fatalf("Stats not what we expected. Expected %+v, got %+v\n", expected, stats)
|
|
}
|
|
|
|
// Now create an observable 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.AddObservable(&server.ObservableConfig{Delivery: delivery, DeliverAll: true, Subject: "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.SetSeqFromReply(m.Reply) != expectedStoreSeq[i] {
|
|
t.Fatalf("Expected store seq of %d, got %d", expectedStoreSeq[i], o.SetSeqFromReply(m.Reply))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJetStreamPubSubPerf(t *testing.T) {
|
|
// Uncomment to run, holding place for now.
|
|
t.SkipNow()
|
|
|
|
s := RunBasicJetStreamServer()
|
|
defer s.Shutdown()
|
|
|
|
config := s.JetStreamConfig()
|
|
if config == nil {
|
|
t.Fatalf("Expected non-nil config")
|
|
}
|
|
defer os.RemoveAll(config.StoreDir)
|
|
|
|
acc := s.GlobalAccount()
|
|
|
|
msetConfig := server.MsgSetConfig{
|
|
Name: "MSET22",
|
|
Storage: server.FileStorage,
|
|
Subjects: []string{"foo"},
|
|
}
|
|
|
|
mset, err := acc.AddMsgSet(&msetConfig)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error adding message set: %v", err)
|
|
}
|
|
|
|
nc := clientConnectToServer(t, s)
|
|
defer nc.Close()
|
|
|
|
var toSend = 1000000
|
|
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.AddObservable(&server.ObservableConfig{
|
|
Delivery: delivery,
|
|
DeliverAll: true,
|
|
AckPolicy: server.AckNone,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Error creating observable: %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())
|
|
}
|