From 6570ee2c8ef74789f5a44578447db04f6f09977b Mon Sep 17 00:00:00 2001 From: Piotr Piotrowski Date: Sun, 15 Oct 2023 18:59:15 +0200 Subject: [PATCH] [FIXED] Get rid of server dev dependencies in nats package (#1441) Signed-off-by: Piotr Piotrowski Co-authored-by: masumomo --- .travis.yml | 2 +- enc_test.go | 314 ---------- go.mod | 1 + go.sum | 4 + js_test.go | 1233 ------------------------------------ kv_test.go | 316 ---------- nats.go | 22 - nats_test.go | 1269 +------------------------------------- norace_test.go | 767 ----------------------- scripts/cov.sh | 2 +- test/auth_test.go | 16 +- test/cluster_test.go | 4 +- test/compat_test.go | 13 + test/conn_test.go | 12 +- test/enc_test.go | 291 ++++++++- test/headers_test.go | 6 +- test/helper_test.go | 10 +- test/js_internal_test.go | 459 ++++++++++++++ test/js_test.go | 739 +++++++++++++++++++++- test/kv_test.go | 252 +++++++- test/nats_test.go | 1141 ++++++++++++++++++++++++++++++++++ test/norace_test.go | 669 +++++++++++++++++++- test/reconnect_test.go | 28 +- test/ws_test.go | 612 ++++++++++++++++++ testing_internal.go | 59 ++ ws_test.go | 608 ------------------ 26 files changed, 4272 insertions(+), 4577 deletions(-) delete mode 100644 enc_test.go delete mode 100644 kv_test.go delete mode 100644 norace_test.go create mode 100644 test/js_internal_test.go create mode 100644 test/nats_test.go create mode 100644 test/ws_test.go create mode 100644 testing_internal.go diff --git a/.travis.yml b/.travis.yml index 368797051..1505f773d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ before_script: - golangci-lint run ./jetstream/... script: - go test -modfile=go_test.mod -v -run=TestNoRace -p=1 ./... --failfast -vet=off -- if [[ "$TRAVIS_GO_VERSION" =~ 1.21 ]]; then ./scripts/cov.sh TRAVIS; else go test -modfile=go_test.mod -race -v -p=1 ./... --failfast -vet=off; fi +- if [[ "$TRAVIS_GO_VERSION" =~ 1.21 ]]; then ./scripts/cov.sh TRAVIS; else go test -modfile=go_test.mod -race -v -p=1 ./... --failfast -vet=off -tags=internal_testing; fi after_success: - if [[ "$TRAVIS_GO_VERSION" =~ 1.21 ]]; then $HOME/gopath/bin/goveralls -coverprofile=acc.out -service travis-ci; fi diff --git a/enc_test.go b/enc_test.go deleted file mode 100644 index 1fdbd274b..000000000 --- a/enc_test.go +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright 2012-2022 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 nats_test - -import ( - "fmt" - "testing" - "time" - - . "github.com/nats-io/nats.go" - "github.com/nats-io/nats.go/encoders/protobuf" - "github.com/nats-io/nats.go/encoders/protobuf/testdata" -) - -// Since we import above nats packages, we need to have a different -// const name than TEST_PORT that we used on the other packages. -const ENC_TEST_PORT = 8268 - -var options = Options{ - Url: fmt.Sprintf("nats://127.0.0.1:%d", ENC_TEST_PORT), - AllowReconnect: true, - MaxReconnect: 10, - ReconnectWait: 100 * time.Millisecond, - Timeout: DefaultTimeout, -} - -//////////////////////////////////////////////////////////////////////////////// -// Encoded connection tests -//////////////////////////////////////////////////////////////////////////////// - -func TestPublishErrorAfterSubscribeDecodeError(t *testing.T) { - ts := RunServerOnPort(ENC_TEST_PORT) - defer ts.Shutdown() - opts := options - nc, _ := opts.Connect() - defer nc.Close() - - // Override default handler for test. - nc.SetErrorHandler(func(_ *Conn, _ *Subscription, _ error) {}) - - c, _ := NewEncodedConn(nc, JSON_ENCODER) - - //Test message type - type Message struct { - Message string - } - const testSubj = "test" - - c.Subscribe(testSubj, func(msg *Message) {}) - - // Publish invalid json to catch decode error in subscription callback - c.Publish(testSubj, `foo`) - c.Flush() - - // Next publish should be successful - if err := c.Publish(testSubj, Message{"2"}); err != nil { - t.Error("Fail to send correct json message after decode error in subscription") - } -} - -func TestPublishErrorAfterInvalidPublishMessage(t *testing.T) { - ts := RunServerOnPort(ENC_TEST_PORT) - defer ts.Shutdown() - opts := options - nc, _ := opts.Connect() - defer nc.Close() - c, _ := NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER) - const testSubj = "test" - - c.Publish(testSubj, &testdata.Person{Name: "Anatolii"}) - - // Publish invalid protobuf message to catch decode error - c.Publish(testSubj, "foo") - - // Next publish with valid protobuf message should be successful - if err := c.Publish(testSubj, &testdata.Person{Name: "Anatolii"}); err != nil { - t.Error("Fail to send correct protobuf message after invalid message publishing", err) - } -} - -func TestVariousFailureConditions(t *testing.T) { - ts := RunServerOnPort(ENC_TEST_PORT) - defer ts.Shutdown() - - dch := make(chan bool) - - opts := options - opts.AsyncErrorCB = func(_ *Conn, _ *Subscription, e error) { - dch <- true - } - nc, _ := opts.Connect() - nc.Close() - - if _, err := NewEncodedConn(nil, protobuf.PROTOBUF_ENCODER); err == nil { - t.Fatal("Expected an error") - } - - if _, err := NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER); err == nil || err != ErrConnectionClosed { - t.Fatalf("Wrong error: %v instead of %v", err, ErrConnectionClosed) - } - - nc, _ = opts.Connect() - defer nc.Close() - - if _, err := NewEncodedConn(nc, "foo"); err == nil { - t.Fatal("Expected an error") - } - - c, err := NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER) - if err != nil { - t.Fatalf("Unable to create encoded connection: %v", err) - } - defer c.Close() - - if _, err := c.Subscribe("bar", func(subj, obj string) {}); err != nil { - t.Fatalf("Unable to create subscription: %v", err) - } - - if err := c.Publish("bar", &testdata.Person{Name: "Ivan"}); err != nil { - t.Fatalf("Unable to publish: %v", err) - } - - if err := Wait(dch); err != nil { - t.Fatal("Did not get the async error callback") - } - - if err := c.PublishRequest("foo", "bar", "foo"); err == nil { - t.Fatal("Expected an error") - } - - if err := c.Request("foo", "foo", nil, 2*time.Second); err == nil { - t.Fatal("Expected an error") - } - - nc.Close() - - if err := c.PublishRequest("foo", "bar", &testdata.Person{Name: "Ivan"}); err == nil { - t.Fatal("Expected an error") - } - - resp := &testdata.Person{} - if err := c.Request("foo", &testdata.Person{Name: "Ivan"}, resp, 2*time.Second); err == nil { - t.Fatal("Expected an error") - } - - if _, err := c.Subscribe("foo", nil); err == nil { - t.Fatal("Expected an error") - } - - if _, err := c.Subscribe("foo", func() {}); err == nil { - t.Fatal("Expected an error") - } - - func() { - defer func() { - if r := recover(); r == nil { - t.Fatal("Expected an error") - } - }() - if _, err := c.Subscribe("foo", "bar"); err == nil { - t.Fatal("Expected an error") - } - }() -} - -func TestRequest(t *testing.T) { - ts := RunServerOnPort(ENC_TEST_PORT) - defer ts.Shutdown() - - dch := make(chan bool) - - opts := options - nc, _ := opts.Connect() - defer nc.Close() - - c, err := NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER) - if err != nil { - t.Fatalf("Unable to create encoded connection: %v", err) - } - defer c.Close() - - sentName := "Ivan" - recvName := "Kozlovic" - - if _, err := c.Subscribe("foo", func(_, reply string, p *testdata.Person) { - if p.Name != sentName { - t.Fatalf("Got wrong name: %v instead of %v", p.Name, sentName) - } - c.Publish(reply, &testdata.Person{Name: recvName}) - dch <- true - }); err != nil { - t.Fatalf("Unable to create subscription: %v", err) - } - if _, err := c.Subscribe("foo", func(_ string, p *testdata.Person) { - if p.Name != sentName { - t.Fatalf("Got wrong name: %v instead of %v", p.Name, sentName) - } - dch <- true - }); err != nil { - t.Fatalf("Unable to create subscription: %v", err) - } - - if err := c.Publish("foo", &testdata.Person{Name: sentName}); err != nil { - t.Fatalf("Unable to publish: %v", err) - } - - if err := Wait(dch); err != nil { - t.Fatal("Did not get message") - } - if err := Wait(dch); err != nil { - t.Fatal("Did not get message") - } - - response := &testdata.Person{} - if err := c.Request("foo", &testdata.Person{Name: sentName}, response, 2*time.Second); err != nil { - t.Fatalf("Unable to publish: %v", err) - } - if response.Name != recvName { - t.Fatalf("Wrong response: %v instead of %v", response.Name, recvName) - } - - if err := Wait(dch); err != nil { - t.Fatal("Did not get message") - } - if err := Wait(dch); err != nil { - t.Fatal("Did not get message") - } - - c2, err := NewEncodedConn(nc, GOB_ENCODER) - if err != nil { - t.Fatalf("Unable to create encoded connection: %v", err) - } - defer c2.Close() - - if _, err := c2.QueueSubscribe("bar", "baz", func(m *Msg) { - response := &Msg{Subject: m.Reply, Data: []byte(recvName)} - c2.Conn.PublishMsg(response) - dch <- true - }); err != nil { - t.Fatalf("Unable to create subscription: %v", err) - } - - mReply := Msg{} - if err := c2.Request("bar", &Msg{Data: []byte(sentName)}, &mReply, 2*time.Second); err != nil { - t.Fatalf("Unable to send request: %v", err) - } - if string(mReply.Data) != recvName { - t.Fatalf("Wrong reply: %v instead of %v", string(mReply.Data), recvName) - } - - if err := Wait(dch); err != nil { - t.Fatal("Did not get message") - } - - if c.LastError() != nil { - t.Fatalf("Unexpected connection error: %v", c.LastError()) - } - if c2.LastError() != nil { - t.Fatalf("Unexpected connection error: %v", c2.LastError()) - } -} - -func TestRequestGOB(t *testing.T) { - ts := RunServerOnPort(ENC_TEST_PORT) - defer ts.Shutdown() - - type Request struct { - Name string - } - - type Person struct { - Name string - Age int - } - - nc, err := Connect(options.Url) - if err != nil { - t.Fatalf("Could not connect: %v", err) - } - defer nc.Close() - - ec, err := NewEncodedConn(nc, GOB_ENCODER) - if err != nil { - t.Fatalf("Unable to create encoded connection: %v", err) - } - defer ec.Close() - - ec.QueueSubscribe("foo.request", "g", func(subject, reply string, r *Request) { - if r.Name != "meg" { - t.Fatalf("Expected request to be 'meg', got %q", r) - } - response := &Person{Name: "meg", Age: 21} - ec.Publish(reply, response) - }) - - reply := Person{} - if err := ec.Request("foo.request", &Request{Name: "meg"}, &reply, time.Second); err != nil { - t.Fatalf("Failed to receive response: %v", err) - } - if reply.Name != "meg" || reply.Age != 21 { - t.Fatalf("Did not receive proper response, %+v", reply) - } -} diff --git a/go.mod b/go.mod index b103e0790..a0181fa72 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/klauspost/compress v1.17.0 github.com/nats-io/nkeys v0.4.5 github.com/nats-io/nuid v1.0.1 + golang.org/x/text v0.13.0 ) require ( diff --git a/go.sum b/go.sum index 111cfe294..7787524b0 100644 --- a/go.sum +++ b/go.sum @@ -8,3 +8,7 @@ golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= diff --git a/js_test.go b/js_test.go index a36d96285..ffbd75db9 100644 --- a/js_test.go +++ b/js_test.go @@ -18,1048 +18,10 @@ package nats //////////////////////////////////////////////////////////////////////////////// import ( - "crypto/sha256" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "math/rand" - "os" - "reflect" "strings" - "sync" - "sync/atomic" "testing" - "time" - - "github.com/nats-io/nats-server/v2/server" - natsserver "github.com/nats-io/nats-server/v2/test" ) -func client(t *testing.T, s *server.Server, opts ...Option) *Conn { - t.Helper() - nc, err := Connect(s.ClientURL(), opts...) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - return nc -} - -func jsClient(t *testing.T, s *server.Server, opts ...Option) (*Conn, JetStreamContext) { - t.Helper() - nc := client(t, s, opts...) - js, err := nc.JetStream(MaxWait(10 * time.Second)) - if err != nil { - t.Fatalf("Unexpected error getting JetStream context: %v", err) - } - return nc, js -} - -func RunBasicJetStreamServer() *server.Server { - opts := natsserver.DefaultTestOptions - opts.Port = -1 - opts.JetStream = true - return natsserver.RunServer(&opts) -} - -func RunServerWithConfig(configFile string) (*server.Server, *server.Options) { - return natsserver.RunServerWithConfig(configFile) -} - -func createConfFile(t *testing.T, content []byte) string { - t.Helper() - conf, err := os.CreateTemp("", "") - if err != nil { - t.Fatalf("Error creating conf file: %v", err) - } - fName := conf.Name() - conf.Close() - if err := os.WriteFile(fName, content, 0666); err != nil { - os.Remove(fName) - t.Fatalf("Error writing conf file: %v", err) - } - return fName -} - -func shutdownJSServerAndRemoveStorage(t *testing.T, s *server.Server) { - t.Helper() - var sd string - if config := s.JetStreamConfig(); config != nil { - sd = config.StoreDir - } - s.Shutdown() - if sd != _EMPTY_ { - if err := os.RemoveAll(sd); err != nil { - t.Fatalf("Unable to remove storage %q: %v", sd, err) - } - } - s.WaitForShutdown() -} - -// Need access to internals for loss testing. -func TestJetStreamOrderedConsumer(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - var err error - _, err = js.AddStream(&StreamConfig{ - Name: "OBJECT", - Subjects: []string{"a"}, - Storage: MemoryStorage, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - // Will be used as start time to validate proper reset to sequence on retries. - startTime := time.Now() - - // Create a sample asset. - msg := make([]byte, 1024*1024) - rand.Read(msg) - msg = []byte(base64.StdEncoding.EncodeToString(msg)) - mlen, sum := len(msg), sha256.Sum256(msg) - - // Now send into the stream as chunks. - const chunkSize = 1024 - for i := 0; i < mlen; i += chunkSize { - var chunk []byte - if mlen-i <= chunkSize { - chunk = msg[i:] - } else { - chunk = msg[i : i+chunkSize] - } - msg := NewMsg("a") - msg.Data = chunk - msg.Header.Set("data", "true") - js.PublishMsgAsync(msg) - } - js.PublishAsync("a", nil) // eof - - select { - case <-js.PublishAsyncComplete(): - case <-time.After(time.Second): - t.Fatalf("Did not receive completion signal") - } - - // Do some tests on simple misconfigurations first. - // For ordered delivery a couple of things need to be set properly. - // Can't be durable or have ack policy that is not ack none or max deliver set. - _, err = js.SubscribeSync("a", OrderedConsumer(), Durable("dlc")) - if err == nil || !strings.Contains(err.Error(), "ordered consumer") { - t.Fatalf("Expected an error, got %v", err) - } - - _, err = js.SubscribeSync("a", OrderedConsumer(), AckExplicit()) - if err == nil || !strings.Contains(err.Error(), "ordered consumer") { - t.Fatalf("Expected an error, got %v", err) - } - - _, err = js.SubscribeSync("a", OrderedConsumer(), MaxDeliver(10)) - if err == nil || !strings.Contains(err.Error(), "ordered consumer") { - t.Fatalf("Expected an error, got %v", err) - } - - _, err = js.SubscribeSync("a", OrderedConsumer(), DeliverSubject("some.subject")) - if err == nil || !strings.Contains(err.Error(), "ordered consumer") { - t.Fatalf("Expected an error, got %v", err) - } - - si, err := js.StreamInfo("OBJECT") - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - testConsumer := func() { - t.Helper() - var received uint32 - var rmsg []byte - done := make(chan bool, 1) - - cb := func(m *Msg) { - // Check for eof - if len(m.Data) == 0 { - done <- true - return - } - atomic.AddUint32(&received, 1) - rmsg = append(rmsg, m.Data...) - } - // OrderedConsumer does not need HB, it sets it on its own, but for test we override which is ok. - sub, err := js.Subscribe("a", cb, OrderedConsumer(), IdleHeartbeat(250*time.Millisecond), StartTime(startTime)) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - defer sub.Unsubscribe() - - select { - case <-done: - if rsum := sha256.Sum256(rmsg); rsum != sum { - t.Fatalf("Objects do not match") - } - case <-time.After(5 * time.Second): - t.Fatalf("Did not receive all chunks, only %d of %d total", atomic.LoadUint32(&received), si.State.Msgs-1) - } - } - - testSyncConsumer := func() { - t.Helper() - var received int - var rmsg []byte - - // OrderedConsumer does not need HB, it sets it on its own, but for test we override which is ok. - sub, err := js.SubscribeSync("a", OrderedConsumer(), IdleHeartbeat(250*time.Millisecond), StartTime(startTime)) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - defer sub.Unsubscribe() - - var done bool - expires := time.Now().Add(5 * time.Second) - for time.Now().Before(expires) { - m, err := sub.NextMsg(time.Second) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - if len(m.Data) == 0 { - done = true - break - } - received++ - rmsg = append(rmsg, m.Data...) - } - if !done { - t.Fatalf("Did not receive all chunks, only %d of %d total", received, si.State.Msgs-1) - } - if rsum := sha256.Sum256(rmsg); rsum != sum { - t.Fatalf("Objects do not match") - } - } - - // Now run normal test. - testConsumer() - testSyncConsumer() - - // Now introduce some loss. - singleLoss := func(m *Msg) *Msg { - if rand.Intn(100) <= 10 && m.Header.Get("data") != _EMPTY_ { - nc.removeMsgFilter("a") - return nil - } - return m - } - nc.addMsgFilter("a", singleLoss) - testConsumer() - nc.addMsgFilter("a", singleLoss) - testSyncConsumer() - - multiLoss := func(m *Msg) *Msg { - if rand.Intn(100) <= 10 && m.Header.Get("data") != _EMPTY_ { - return nil - } - return m - } - nc.addMsgFilter("a", multiLoss) - testConsumer() - testSyncConsumer() - - firstOnly := func(m *Msg) *Msg { - if meta, err := m.Metadata(); err == nil { - if meta.Sequence.Consumer == 1 { - nc.removeMsgFilter("a") - return nil - } - } - return m - } - nc.addMsgFilter("a", firstOnly) - testConsumer() - nc.addMsgFilter("a", firstOnly) - testSyncConsumer() - - lastOnly := func(m *Msg) *Msg { - if meta, err := m.Metadata(); err == nil { - if meta.Sequence.Stream >= si.State.LastSeq-1 { - nc.removeMsgFilter("a") - return nil - } - } - return m - } - nc.addMsgFilter("a", lastOnly) - testConsumer() - nc.addMsgFilter("a", lastOnly) - testSyncConsumer() -} - -func TestJetStreamOrderedConsumerDeleteAssets(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - var err error - - // For capturing errors. - errCh := make(chan error, 1) - nc.SetErrorHandler(func(_ *Conn, _ *Subscription, err error) { - errCh <- err - }) - - // Create a sample asset. - mlen := 128 * 1024 - msg := make([]byte, mlen) - - createStream := func() { - t.Helper() - _, err = js.AddStream(&StreamConfig{ - Name: "OBJECT", - Subjects: []string{"a"}, - Storage: MemoryStorage, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - // Now send into the stream as chunks. - const chunkSize = 256 - for i := 0; i < mlen; i += chunkSize { - var chunk []byte - if mlen-i <= chunkSize { - chunk = msg[i:] - } else { - chunk = msg[i : i+chunkSize] - } - js.PublishAsync("a", chunk) - } - select { - case <-js.PublishAsyncComplete(): - case <-time.After(time.Second): - t.Fatalf("Did not receive completion signal") - } - } - - t.Run("remove stream, expect error", func(t *testing.T) { - createStream() - - sub, err := js.SubscribeSync("a", OrderedConsumer(), IdleHeartbeat(200*time.Millisecond)) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - defer sub.Unsubscribe() - - // Since we are sync we will be paused here due to flow control. - time.Sleep(100 * time.Millisecond) - // Now delete the asset and make sure we get an error. - if err := js.DeleteStream("OBJECT"); err != nil { - t.Fatalf("Unexpected error: %v", err) - } - // Make sure we get an error. - select { - case err := <-errCh: - if !errors.Is(err, ErrStreamNotFound) { - t.Fatalf("Got wrong error, wanted %v, got %v", ErrStreamNotFound, err) - } - case <-time.After(time.Second): - t.Fatalf("Did not receive err message as expected") - } - }) - - t.Run("remove consumer, expect it to be recreated", func(t *testing.T) { - createStream() - - createConsSub, err := nc.SubscribeSync("$JS.API.CONSUMER.CREATE.OBJECT.*.a") - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - defer createConsSub.Unsubscribe() - // Again here the IdleHeartbeat is not required, just overriding top shorten test time. - sub, err := js.SubscribeSync("a", OrderedConsumer(), IdleHeartbeat(200*time.Millisecond)) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - defer sub.Unsubscribe() - - createConsMsg, err := createConsSub.NextMsg(time.Second) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - if !strings.Contains(string(createConsMsg.Data), `"stream_name":"OBJECT"`) { - t.Fatalf("Invalid message on create consumer subject: %q", string(createConsMsg.Data)) - } - - time.Sleep(100 * time.Millisecond) - ci, err := sub.ConsumerInfo() - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - consName := ci.Name - - if err := js.DeleteConsumer("OBJECT", consName); err != nil { - t.Fatalf("Unexpected error: %v", err) - } - createConsMsg, err = createConsSub.NextMsg(time.Second) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - if !strings.Contains(string(createConsMsg.Data), `"stream_name":"OBJECT"`) { - t.Fatalf("Invalid message on create consumer subject: %q", string(createConsMsg.Data)) - } - - time.Sleep(100 * time.Millisecond) - ci, err = sub.ConsumerInfo() - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - newConsName := ci.Name - if consName == newConsName { - t.Fatalf("Consumer should be recreated, but consumer name is the same") - } - }) -} - -func TestJetStreamOrderedConsumerWithAutoUnsub(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - var err error - - _, err = js.AddStream(&StreamConfig{ - Name: "OBJECT", - Subjects: []string{"a"}, - Storage: MemoryStorage, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - count := int32(0) - sub, err := js.Subscribe("a", func(m *Msg) { - atomic.AddInt32(&count, 1) - }, OrderedConsumer(), IdleHeartbeat(250*time.Millisecond)) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - // Ask to auto-unsub after 10 messages. - sub.AutoUnsubscribe(10) - - // Set a message filter that will drop 1 message - dm := 0 - singleLoss := func(m *Msg) *Msg { - if m.Header.Get("data") != _EMPTY_ { - dm++ - if dm == 5 { - nc.removeMsgFilter("a") - return nil - } - } - return m - } - nc.addMsgFilter("a", singleLoss) - - // Now produce 20 messages - for i := 0; i < 20; i++ { - msg := NewMsg("a") - msg.Data = []byte(fmt.Sprintf("msg_%d", i+1)) - msg.Header.Set("data", "true") - js.PublishMsgAsync(msg) - } - - select { - case <-js.PublishAsyncComplete(): - case <-time.After(time.Second): - t.Fatalf("Did not receive completion signal") - } - - // Wait for the subscription to be marked as invalid - deadline := time.Now().Add(time.Second) - ok := false - for time.Now().Before(deadline) { - if !sub.IsValid() { - ok = true - break - } - } - if !ok { - t.Fatalf("Subscription still valid") - } - - // Wait a bit to make sure we are not receiving more than expected, - // and give a chance for the server to process the auto-unsub - // protocol. - time.Sleep(500 * time.Millisecond) - - if n := atomic.LoadInt32(&count); n != 10 { - t.Fatalf("Sub should have received only 10 messages, got %v", n) - } - - // Now capture the in msgs count for the connection - inMsgs := nc.Stats().InMsgs - - // Send one more message and this count should not increase if the - // server had properly processed the auto-unsub after the - // reset of the ordered consumer. Use a different connection - // to send. - nc2, js2 := jsClient(t, s) - defer nc2.Close() - - js2.Publish("a", []byte("should not be received")) - - newInMsgs := nc.Stats().InMsgs - if inMsgs != newInMsgs { - t.Fatal("Seems that AUTO-UNSUB was not properly handled") - } -} - -// We want to make sure we do the right thing with lots of concurrent queue durable consumer requests. -// One should win and the others should share the delivery subject with the first one who wins. -func TestJetStreamConcurrentQueueDurablePushConsumers(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - var err error - - // Create stream. - _, err = js.AddStream(&StreamConfig{ - Name: "TEST", - Subjects: []string{"foo"}, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - // Now create 10 durables concurrently. - subs := make([]*Subscription, 0, 10) - var wg sync.WaitGroup - mx := &sync.Mutex{} - - for i := 0; i < 10; i++ { - wg.Add(1) - go func() { - defer wg.Done() - sub, _ := js.QueueSubscribeSync("foo", "bar") - mx.Lock() - subs = append(subs, sub) - mx.Unlock() - }() - } - // Wait for all the consumers. - wg.Wait() - - si, err := js.StreamInfo("TEST") - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - if si.State.Consumers != 1 { - t.Fatalf("Expected exactly one consumer, got %d", si.State.Consumers) - } - - // Now send some messages and make sure they are distributed. - total := 1000 - for i := 0; i < total; i++ { - js.Publish("foo", []byte("Hello")) - } - - timeout := time.Now().Add(2 * time.Second) - got := 0 - for time.Now().Before(timeout) { - got = 0 - for _, sub := range subs { - pending, _, _ := sub.Pending() - // If a single sub has the total, then probably something is not right. - if pending == total { - t.Fatalf("A single member should not have gotten all messages") - } - got += pending - } - if got == total { - // We are done! - return - } - } - t.Fatalf("Expected %v messages, got only %v", total, got) -} - -func TestJetStreamSubscribeReconnect(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - rch := make(chan struct{}, 1) - nc, err := Connect(s.ClientURL(), - ReconnectWait(50*time.Millisecond), - ReconnectHandler(func(_ *Conn) { - select { - case rch <- struct{}{}: - default: - } - })) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - defer nc.Close() - - js, err := nc.JetStream(MaxWait(250 * time.Millisecond)) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - // Create the stream using our client API. - _, err = js.AddStream(&StreamConfig{ - Name: "TEST", - Subjects: []string{"foo"}, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - sub, err := js.SubscribeSync("foo", Durable("bar")) - if err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - - sendAndReceive := func(msgContent string) { - t.Helper() - var ok bool - var err error - for i := 0; i < 5; i++ { - if _, err = js.Publish("foo", []byte(msgContent)); err != nil { - time.Sleep(250 * time.Millisecond) - continue - } - ok = true - break - } - if !ok { - t.Fatalf("Error on publish: %v", err) - } - msg, err := sub.NextMsg(time.Second) - if err != nil { - t.Fatal("Did not get message") - } - if string(msg.Data) != msgContent { - t.Fatalf("Unexpected content: %q", msg.Data) - } - if err := msg.AckSync(); err != nil { - t.Fatalf("Error on ack: %v", err) - } - } - - sendAndReceive("msg1") - - // Cause a disconnect... - nc.mu.Lock() - nc.conn.Close() - nc.mu.Unlock() - - // Wait for reconnect - select { - case <-rch: - case <-time.After(time.Second): - t.Fatal("Did not reconnect") - } - - // Make sure we can send and receive the msg - sendAndReceive("msg2") -} - -func TestJetStreamAckTokens(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - var err error - - // Create the stream using our client API. - _, err = js.AddStream(&StreamConfig{ - Name: "TEST", - Subjects: []string{"foo"}, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - sub, err := js.SubscribeSync("foo") - if err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - - now := time.Now() - for _, test := range []struct { - name string - expected *MsgMetadata - str string - end string - err bool - }{ - { - "valid token size but not js ack", - nil, - "1.2.3.4.5.6.7.8.9", - "", - true, - }, - { - "valid token size but not js ack", - nil, - "1.2.3.4.5.6.7.8.9.10.11.12", - "", - true, - }, - { - "invalid token size", - nil, - "$JS.ACK.3.4.5.6.7.8", - "", - true, - }, - { - "invalid token size", - nil, - "$JS.ACK.3.4.5.6.7.8.9.10", - "", - true, - }, - { - "v1 style", - &MsgMetadata{ - Stream: "TEST", - Consumer: "cons", - NumDelivered: 1, - Sequence: SequencePair{ - Stream: 2, - Consumer: 3, - }, - Timestamp: now, - NumPending: 4, - }, - "", - "", - false, - }, - { - "v2 style no domain with hash", - &MsgMetadata{ - Stream: "TEST", - Consumer: "cons", - NumDelivered: 1, - Sequence: SequencePair{ - Stream: 2, - Consumer: 3, - }, - Timestamp: now, - NumPending: 4, - }, - "_.ACCHASH.", - ".abcde", - false, - }, - { - "v2 style with domain and hash", - &MsgMetadata{ - Domain: "HUB", - Stream: "TEST", - Consumer: "cons", - NumDelivered: 1, - Sequence: SequencePair{ - Stream: 2, - Consumer: 3, - }, - Timestamp: now, - NumPending: 4, - }, - "HUB.ACCHASH.", - ".abcde", - false, - }, - { - "more than 12 tokens", - &MsgMetadata{ - Domain: "HUB", - Stream: "TEST", - Consumer: "cons", - NumDelivered: 1, - Sequence: SequencePair{ - Stream: 2, - Consumer: 3, - }, - Timestamp: now, - NumPending: 4, - }, - "HUB.ACCHASH.", - ".abcde.ghijk.lmnop", - false, - }, - } { - t.Run(test.name, func(t *testing.T) { - msg := NewMsg("foo") - msg.Sub = sub - if test.err { - msg.Reply = test.str - } else { - msg.Reply = fmt.Sprintf("$JS.ACK.%sTEST.cons.1.2.3.%v.4%s", test.str, now.UnixNano(), test.end) - } - - meta, err := msg.Metadata() - if test.err { - if err == nil || meta != nil { - t.Fatalf("Expected error for content: %q, got meta=%+v err=%v", test.str, meta, err) - } - // Expected error, we are done - return - } - if err != nil { - t.Fatalf("Expected: %+v with reply: %q, got error %v", test.expected, msg.Reply, err) - } - if meta.Timestamp.UnixNano() != now.UnixNano() { - t.Fatalf("Timestamp is bad: %v vs %v", now.UnixNano(), meta.Timestamp.UnixNano()) - } - meta.Timestamp = time.Time{} - test.expected.Timestamp = time.Time{} - if !reflect.DeepEqual(test.expected, meta) { - t.Fatalf("Expected %+v, got %+v", test.expected, meta) - } - }) - } -} - -func TestJetStreamFlowControlStalled(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - var err error - - _, err = js.AddStream(&StreamConfig{ - Name: "TEST", - Subjects: []string{"a"}, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - if _, err := js.SubscribeSync("a", - DeliverSubject("ds"), - Durable("dur"), - IdleHeartbeat(200*time.Millisecond), - EnableFlowControl()); err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - - // Drop all incoming FC control messages. - fcLoss := func(m *Msg) *Msg { - if _, ctrlType := isJSControlMessage(m); ctrlType == jsCtrlFC { - return nil - } - return m - } - nc.addMsgFilter("ds", fcLoss) - - // Have a subscription on the FC subject to make sure that the library - // respond to the requests for un-stall - checkSub, err := nc.SubscribeSync("$JS.FC.>") - if err != nil { - t.Fatalf("Error on sub: %v", err) - } - - // Publish bunch of messages. - payload := make([]byte, 100*1024) - for i := 0; i < 250; i++ { - nc.Publish("a", payload) - } - - // Now wait that we respond to a stalled FC - if _, err := checkSub.NextMsg(2 * time.Second); err != nil { - t.Fatal("Library did not send FC") - } -} - -func TestJetStreamTracing(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, err := Connect(s.ClientURL()) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - defer nc.Close() - - ctr := 0 - js, err := nc.JetStream(&ClientTrace{ - RequestSent: func(subj string, payload []byte) { - ctr++ - if subj != "$JS.API.STREAM.CREATE.X" { - t.Fatalf("Expected sent trace to %s: got: %s", "$JS.API.STREAM.CREATE.X", subj) - } - }, - ResponseReceived: func(subj string, payload []byte, hdr Header) { - ctr++ - if subj != "$JS.API.STREAM.CREATE.X" { - t.Fatalf("Expected received trace to %s: got: %s", "$JS.API.STREAM.CREATE.X", subj) - } - }, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - _, err = js.AddStream(&StreamConfig{Name: "X"}) - if err != nil { - t.Fatalf("add stream failed: %s", err) - } - if ctr != 2 { - t.Fatalf("did not receive all trace events: %d", ctr) - } -} - -func TestJetStreamExpiredPullRequests(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - var err error - - _, err = js.AddStream(&StreamConfig{ - Name: "TEST", - Subjects: []string{"foo"}, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - sub, err := js.PullSubscribe("foo", "bar", PullMaxWaiting(2)) - if err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - // Make sure that we reject batch < 1 - if _, err := sub.Fetch(0); err == nil { - t.Fatal("Expected error, did not get one") - } - if _, err := sub.Fetch(-1); err == nil { - t.Fatal("Expected error, did not get one") - } - - // Send 2 fetch requests - for i := 0; i < 2; i++ { - if _, err = sub.Fetch(1, MaxWait(15*time.Millisecond)); err == nil { - t.Fatalf("Expected error, got none") - } - } - // Wait before the above expire - time.Sleep(50 * time.Millisecond) - batches := []int{1, 10} - for _, bsz := range batches { - start := time.Now() - _, err = sub.Fetch(bsz, MaxWait(250*time.Millisecond)) - dur := time.Since(start) - if err == nil || dur < 50*time.Millisecond { - t.Fatalf("Expected error and wait for 250ms, got err=%v and dur=%v", err, dur) - } - } -} - -func TestJetStreamSyncSubscribeWithMaxAckPending(t *testing.T) { - opts := natsserver.DefaultTestOptions - opts.Port = -1 - opts.JetStream = true - opts.JetStreamLimits.MaxAckPending = 123 - s := natsserver.RunServer(&opts) - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - if _, err := js.AddStream(&StreamConfig{Name: "MAX_ACK_PENDING", Subjects: []string{"foo"}}); err != nil { - t.Fatalf("Error adding stream: %v", err) - } - - // By default, the sync subscription will be created with a MaxAckPending equal - // to the internal sync queue len, which is 64K. So that should error out - // and make sure we get the actual limit - - checkSub := func(pull bool) { - var sub *Subscription - var err error - if pull { - _, err = js.PullSubscribe("foo", "bar") - } else { - _, err = js.SubscribeSync("foo") - } - if err == nil || !strings.Contains(err.Error(), "system limit of 123") { - t.Fatalf("Unexpected error: %v", err) - } - - // But it should work if we use MaxAckPending() with lower value - if pull { - sub, err = js.PullSubscribe("foo", "bar", MaxAckPending(64)) - } else { - sub, err = js.SubscribeSync("foo", MaxAckPending(64)) - } - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - sub.Unsubscribe() - } - checkSub(false) - checkSub(true) -} - -func TestJetStreamClusterPlacement(t *testing.T) { - // There used to be a test here that would not work because it would require - // all servers in the cluster to know about each other tags. So we will simply - // verify that if a stream is configured with placement and tags, the proper - // "stream create" request is sent. - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - sub, err := nc.SubscribeSync(fmt.Sprintf("$JS.API."+apiStreamCreateT, "TEST")) - if err != nil { - t.Fatalf("Error on sub: %v", err) - } - js.AddStream(&StreamConfig{ - Name: "TEST", - Placement: &Placement{ - Tags: []string{"my_tag"}, - }, - }) - msg, err := sub.NextMsg(time.Second) - if err != nil { - t.Fatalf("Error getting stream create request: %v", err) - } - var req StreamConfig - if err := json.Unmarshal(msg.Data, &req); err != nil { - t.Fatalf("Unmarshal error: %v", err) - } - if req.Placement == nil { - t.Fatal("Expected placement, did not get it") - } - if n := len(req.Placement.Tags); n != 1 { - t.Fatalf("Expected 1 tag, got %v", n) - } - if v := req.Placement.Tags[0]; v != "my_tag" { - t.Fatalf("Unexpected tag: %q", v) - } -} - func TestJetStreamConvertDirectMsgResponseToMsg(t *testing.T) { // This test checks the conversion of a "direct get message" response // to a JS message based on the content of specific NATS headers. @@ -1129,198 +91,3 @@ func TestJetStreamConvertDirectMsgResponseToMsg(t *testing.T) { t.Fatalf("Wrong header: %v", r.Header) } } - -func TestJetStreamConsumerMemoryStorage(t *testing.T) { - opts := natsserver.DefaultTestOptions - opts.Port = -1 - opts.JetStream = true - s := natsserver.RunServer(&opts) - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - if _, err := js.AddStream(&StreamConfig{Name: "STR", Subjects: []string{"foo"}}); err != nil { - t.Fatalf("Error adding stream: %v", err) - } - - // Pull ephemeral consumer with memory storage. - sub, err := js.PullSubscribe("foo", "", ConsumerMemoryStorage()) - if err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - - consInfo, err := sub.ConsumerInfo() - if err != nil { - t.Fatalf("Error getting consumer info: %v", err) - } - - if !consInfo.Config.MemoryStorage { - t.Fatalf("Expected memory storage to be %v, got %+v", true, consInfo.Config.MemoryStorage) - } - - // Create a sync subscription with an in-memory ephemeral consumer. - sub, err = js.SubscribeSync("foo", ConsumerMemoryStorage()) - if err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - - consInfo, err = sub.ConsumerInfo() - if err != nil { - t.Fatalf("Error getting consumer info: %v", err) - } - - if !consInfo.Config.MemoryStorage { - t.Fatalf("Expected memory storage to be %v, got %+v", true, consInfo.Config.MemoryStorage) - } - - // Async subscription with an in-memory ephemeral consumer. - cb := func(msg *Msg) {} - sub, err = js.Subscribe("foo", cb, ConsumerMemoryStorage()) - if err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - - consInfo, err = sub.ConsumerInfo() - if err != nil { - t.Fatalf("Error getting consumer info: %v", err) - } - - if !consInfo.Config.MemoryStorage { - t.Fatalf("Expected memory storage to be %v, got %+v", true, consInfo.Config.MemoryStorage) - } -} - -func TestJetStreamStreamInfoWithSubjectDetails(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - var err error - - _, err = js.AddStream(&StreamConfig{ - Name: "TEST", - Subjects: []string{"test.*"}, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - // Publish on enough subjects to exercise the pagination - payload := make([]byte, 10) - for i := 0; i < 100001; i++ { - _, err := js.Publish(fmt.Sprintf("test.%d", i), payload) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - } - - // Check that passing a filter returns the subject details - result, err := js.StreamInfo("TEST", &StreamInfoRequest{SubjectsFilter: ">"}) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - if len(result.State.Subjects) != 100001 { - t.Fatalf("expected 100001 subjects in the stream, but got %d instead", len(result.State.Subjects)) - } - - // Check that passing no filter does not return any subject details - result, err = js.StreamInfo("TEST") - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - if len(result.State.Subjects) != 0 { - t.Fatalf("expected 0 subjects details from StreamInfo, but got %d instead", len(result.State.Subjects)) - } -} - -func TestStreamNameBySubject(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - var err error - - _, err = js.AddStream(&StreamConfig{ - Name: "TEST", - Subjects: []string{"test.*"}, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - for _, test := range []struct { - name string - streamName string - err error - }{ - - {name: "valid wildcard lookup", streamName: "test.*", err: nil}, - {name: "valid explicit lookup", streamName: "test.a", err: nil}, - {name: "lookup on not existing stream", streamName: "not.existing", err: ErrNoMatchingStream}, - } { - - stream, err := js.StreamNameBySubject(test.streamName) - if err != test.err { - t.Fatalf("expected %v, got %v", test.err, err) - } - - if stream != "TEST" && err == nil { - t.Fatalf("returned stream name should be 'TEST'") - } - } -} - -func TestJetStreamTransform(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - _, err := js.AddStream(&StreamConfig{ - Name: "ORIGIN", - Subjects: []string{"test"}, - SubjectTransform: &SubjectTransformConfig{Source: ">", Destination: "transformed.>"}, - Storage: MemoryStorage, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - err = nc.Publish("test", []byte("1")) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - _, err = js.AddStream(&StreamConfig{ - Subjects: []string{}, - Name: "SOURCING", - Sources: []*StreamSource{{Name: "ORIGIN", SubjectTransforms: []SubjectTransformConfig{{Source: ">", Destination: "fromtest.>"}}}}, - Storage: MemoryStorage, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - // Create a sync subscription with an in-memory ephemeral consumer. - sub, err := js.SubscribeSync("fromtest.>", ConsumerMemoryStorage(), BindStream("SOURCING")) - if err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - - m, err := sub.NextMsg(time.Second) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - if m.Subject != "fromtest.transformed.test" { - t.Fatalf("the subject of the message doesn't match the expected fromtest.transformed.test: %s", m.Subject) - } - -} diff --git a/kv_test.go b/kv_test.go deleted file mode 100644 index 3e0d4843c..000000000 --- a/kv_test.go +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright 2022 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 nats - -import ( - "errors" - "fmt" - "testing" - "time" -) - -func TestKeyValueDiscardOldToDiscardNew(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - checkDiscard := func(expected DiscardPolicy) KeyValue { - t.Helper() - kv, err := js.CreateKeyValue(&KeyValueConfig{Bucket: "TEST", History: 1}) - if err != nil { - t.Fatalf("Error creating store: %v", err) - } - si, err := js.StreamInfo("KV_TEST") - if err != nil { - t.Fatalf("Error getting stream info: %v", err) - } - if si.Config.Discard != expected { - t.Fatalf("Expected discard policy %v, got %+v", expected, si) - } - return kv - } - - // We are going to go from 2.7.1->2.7.2->2.7.1 and 2.7.2 again. - for i := 0; i < 2; i++ { - // Change the server version in the connection to - // create as-if we were connecting to a v2.7.1 server. - nc.mu.Lock() - nc.info.Version = "2.7.1" - nc.mu.Unlock() - - kv := checkDiscard(DiscardOld) - if i == 0 { - if _, err := kv.PutString("foo", "value"); err != nil { - t.Fatalf("Error adding key: %v", err) - } - } - - // Now change version to 2.7.2 - nc.mu.Lock() - nc.info.Version = "2.7.2" - nc.mu.Unlock() - - kv = checkDiscard(DiscardNew) - // Make sure the key still exists - if e, err := kv.Get("foo"); err != nil || string(e.Value()) != "value" { - t.Fatalf("Error getting key: err=%v e=%+v", err, e) - } - } -} - -func TestKeyValueNonDirectGet(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - kvi, err := js.CreateKeyValue(&KeyValueConfig{Bucket: "TEST"}) - if err != nil { - t.Fatalf("Error creating store: %v", err) - } - si, err := js.StreamInfo("KV_TEST") - if err != nil { - t.Fatalf("Error getting stream info: %v", err) - } - if !si.Config.AllowDirect { - t.Fatal("Expected allow direct to be set, it was not") - } - - kv := kvi.(*kvs) - if !kv.useDirect { - t.Fatal("useDirect should have been true, it was not") - } - kv.useDirect = false - - if _, err := kv.PutString("key1", "val1"); err != nil { - t.Fatalf("Error putting key: %v", err) - } - if _, err := kv.PutString("key2", "val2"); err != nil { - t.Fatalf("Error putting key: %v", err) - } - if v, err := kv.Get("key2"); err != nil || string(v.Value()) != "val2" { - t.Fatalf("Error on get: v=%+v err=%v", v, err) - } - if v, err := kv.GetRevision("key1", 1); err != nil || string(v.Value()) != "val1" { - t.Fatalf("Error on get revisiong: v=%+v err=%v", v, err) - } - if v, err := kv.GetRevision("key1", 2); err == nil { - t.Fatalf("Expected error, got %+v", v) - } -} - -func TestKeyValueRePublish(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - if _, err := js.CreateKeyValue(&KeyValueConfig{ - Bucket: "TEST_UPDATE", - }); err != nil { - t.Fatalf("Error creating store: %v", err) - } - // This is expected to fail since server does not support as of now - // the update of RePublish. - if _, err := js.CreateKeyValue(&KeyValueConfig{ - Bucket: "TEST_UPDATE", - RePublish: &RePublish{Source: ">", Destination: "bar.>"}, - }); err == nil { - t.Fatal("Expected failure, did not get one") - } - - kv, err := js.CreateKeyValue(&KeyValueConfig{ - Bucket: "TEST", - RePublish: &RePublish{Source: ">", Destination: "bar.>"}, - }) - if err != nil { - t.Fatalf("Error creating store: %v", err) - } - si, err := js.StreamInfo("KV_TEST") - if err != nil { - t.Fatalf("Error getting stream info: %v", err) - } - if si.Config.RePublish == nil { - t.Fatal("Expected republish to be set, it was not") - } - - sub, err := nc.SubscribeSync("bar.>") - if err != nil { - t.Fatalf("Error on sub: %v", err) - } - if _, err := kv.Put("foo", []byte("value")); err != nil { - t.Fatalf("Error on put: %v", err) - } - msg, err := sub.NextMsg(time.Second) - if err != nil { - t.Fatalf("Error on next: %v", err) - } - if v := string(msg.Data); v != "value" { - t.Fatalf("Unexpected value: %s", v) - } - // The message should also have a header with the actual subject - expected := fmt.Sprintf(kvSubjectsPreTmpl, "TEST") + "foo" - if v := msg.Header.Get(JSSubject); v != expected { - t.Fatalf("Expected subject header %q, got %q", expected, v) - } -} - -func TestKeyValueMirrorDirectGet(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - kv, err := js.CreateKeyValue(&KeyValueConfig{Bucket: "TEST"}) - if err != nil { - t.Fatalf("Error creating kv: %v", err) - } - _, err = js.AddStream(&StreamConfig{ - Name: "MIRROR", - Mirror: &StreamSource{Name: "KV_TEST"}, - MirrorDirect: true, - }) - if err != nil { - t.Fatalf("Error creating mirror: %v", err) - } - - for i := 0; i < 100; i++ { - key := fmt.Sprintf("KEY.%d", i) - if _, err := kv.PutString(key, "42"); err != nil { - t.Fatalf("Error adding key: %v", err) - } - } - - // Make sure all gets work. - for i := 0; i < 100; i++ { - if _, err := kv.Get("KEY.22"); err != nil { - t.Fatalf("Got error getting key: %v", err) - } - } -} - -func TestKeyValueCreate(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - kv, err := js.CreateKeyValue(&KeyValueConfig{Bucket: "TEST"}) - if err != nil { - t.Fatalf("Error creating kv: %v", err) - } - - _, err = kv.Create("key", []byte("1")) - if err != nil { - t.Fatalf("Error creating key: %v", err) - } - - _, err = kv.Create("key", []byte("1")) - expected := "nats: wrong last sequence: 1: key exists" - if err.Error() != expected { - t.Fatalf("Expected %q, got: %v", expected, err) - } - if !errors.Is(err, ErrKeyExists) { - t.Fatalf("Expected ErrKeyExists, got: %v", err) - } - aerr := &APIError{} - if !errors.As(err, &aerr) { - t.Fatalf("Expected APIError, got: %v", err) - } - if aerr.Description != "wrong last sequence: 1" { - t.Fatalf("Unexpected APIError message, got: %v", aerr.Description) - } - if aerr.ErrorCode != 10071 { - t.Fatalf("Unexpected error code, got: %v", aerr.ErrorCode) - } - if aerr.Code != ErrKeyExists.APIError().Code { - t.Fatalf("Unexpected error code, got: %v", aerr.Code) - } - var kerr JetStreamError - if !errors.As(err, &kerr) { - t.Fatalf("Expected KeyValueError, got: %v", err) - } - if kerr.APIError().ErrorCode != 10071 { - t.Fatalf("Unexpected error code, got: %v", kerr.APIError().ErrorCode) - } -} - -func TestKeyValueSourcing(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - kvA, err := js.CreateKeyValue(&KeyValueConfig{Bucket: "A"}) - if err != nil { - t.Fatalf("Error creating kv: %v", err) - } - - _, err = kvA.Create("keyA", []byte("1")) - if err != nil { - t.Fatalf("Error creating key: %v", err) - } - - if _, err := kvA.Get("keyA"); err != nil { - t.Fatalf("Got error getting keyA from A: %v", err) - } - - kvB, err := js.CreateKeyValue(&KeyValueConfig{Bucket: "B"}) - if err != nil { - t.Fatalf("Error creating kv: %v", err) - } - - _, err = kvB.Create("keyB", []byte("1")) - if err != nil { - t.Fatalf("Error creating key: %v", err) - } - - kvC, err := js.CreateKeyValue(&KeyValueConfig{Bucket: "C", Sources: []*StreamSource{{Name: "A"}, {Name: "B"}}}) - if err != nil { - t.Fatalf("Error creating kv: %v", err) - } - - // Wait half a second to make sure it has time to populate the stream from it's sources - i := 0 - for { - status, err := kvC.Status() - if err != nil { - t.Fatalf("Error getting bucket status: %v", err) - } - if status.Values() == 2 { - break - } else { - i++ - if i > 3 { - t.Fatalf("Error sourcing bucket does not contain the expected number of values") - } - } - time.Sleep(20 * time.Millisecond) - } - - if _, err := kvC.Get("keyA"); err != nil { - t.Fatalf("Got error getting keyA from C: %v", err) - } - - if _, err := kvC.Get("keyB"); err != nil { - t.Fatalf("Got error getting keyB from C: %v", err) - } -} diff --git a/nats.go b/nats.go index 73709db88..0031dcb65 100644 --- a/nats.go +++ b/nats.go @@ -3077,28 +3077,6 @@ func (nc *Conn) waitForMsgs(s *Subscription) { // Return what is to be used. If we return nil the message will be dropped. type msgFilter func(m *Msg) *Msg -func (nc *Conn) addMsgFilter(subject string, filter msgFilter) { - nc.subsMu.Lock() - defer nc.subsMu.Unlock() - - if nc.filters == nil { - nc.filters = make(map[string]msgFilter) - } - nc.filters[subject] = filter -} - -func (nc *Conn) removeMsgFilter(subject string) { - nc.subsMu.Lock() - defer nc.subsMu.Unlock() - - if nc.filters != nil { - delete(nc.filters, subject) - if len(nc.filters) == 0 { - nc.filters = nil - } - } -} - // processMsg is called by parse and will place the msg on the // appropriate channel/pending queue for processing. If the channel is full, // or the pending queue is over the pending limits, the connection is diff --git a/nats_test.go b/nats_test.go index b70a190cb..7dcdce2cf 100644 --- a/nats_test.go +++ b/nats_test.go @@ -1,4 +1,4 @@ -// Copyright 2012-2022 The NATS Authors +// Copyright 2012-2023 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 @@ -25,7 +25,6 @@ import ( "fmt" "net" "net/http" - "net/url" "os" "reflect" "regexp" @@ -37,8 +36,6 @@ import ( "testing" "time" - "github.com/nats-io/nats-server/v2/server" - natsserver "github.com/nats-io/nats-server/v2/test" "github.com/nats-io/nkeys" ) @@ -209,58 +206,6 @@ func TestExpandPath(t *testing.T) { } } -//////////////////////////////////////////////////////////////////////////////// -// Reconnect tests -//////////////////////////////////////////////////////////////////////////////// - -const TEST_PORT = 8368 - -var reconnectOpts = Options{ - Url: fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT), - AllowReconnect: true, - MaxReconnect: 10, - ReconnectWait: 100 * time.Millisecond, - Timeout: DefaultTimeout, -} - -func RunServerOnPort(port int) *server.Server { - opts := natsserver.DefaultTestOptions - opts.Port = port - return RunServerWithOptions(&opts) -} - -func RunServerWithOptions(opts *server.Options) *server.Server { - return natsserver.RunServer(opts) -} - -func TestReconnectServerStats(t *testing.T) { - ts := RunServerOnPort(TEST_PORT) - - opts := reconnectOpts - nc, _ := opts.Connect() - defer nc.Close() - nc.Flush() - - ts.Shutdown() - // server is stopped here... - - ts = RunServerOnPort(TEST_PORT) - defer ts.Shutdown() - - if err := nc.FlushTimeout(5 * time.Second); err != nil { - t.Fatalf("Error on Flush: %v", err) - } - - // Make sure the server who is reconnected has the reconnects stats reset. - nc.mu.Lock() - _, cur := nc.currentServer() - nc.mu.Unlock() - - if cur.reconnects != 0 { - t.Fatalf("Current Server's reconnects should be 0 vs %d\n", cur.reconnects) - } -} - //////////////////////////////////////////////////////////////////////////////// // ServerPool tests //////////////////////////////////////////////////////////////////////////////// @@ -275,86 +220,6 @@ var testServers = []string{ "nats://localhost:1228", } -func TestMaxConnectionsReconnect(t *testing.T) { - - // Start first server - s1Opts := natsserver.DefaultTestOptions - s1Opts.Port = -1 - s1Opts.MaxConn = 2 - s1Opts.Cluster = server.ClusterOpts{Name: "test", Host: "127.0.0.1", Port: -1} - s1 := RunServerWithOptions(&s1Opts) - defer s1.Shutdown() - - // Start second server - s2Opts := natsserver.DefaultTestOptions - s2Opts.Port = -1 - s2Opts.MaxConn = 2 - s2Opts.Cluster = server.ClusterOpts{Name: "test", Host: "127.0.0.1", Port: -1} - s2Opts.Routes = server.RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", s1Opts.Cluster.Port)) - s2 := RunServerWithOptions(&s2Opts) - defer s2.Shutdown() - - errCh := make(chan error, 2) - reconnectCh := make(chan struct{}) - opts := []Option{ - MaxReconnects(2), - ReconnectWait(10 * time.Millisecond), - Timeout(200 * time.Millisecond), - DisconnectErrHandler(func(_ *Conn, err error) { - if err != nil { - errCh <- err - } - }), - ReconnectHandler(func(_ *Conn) { - reconnectCh <- struct{}{} - }), - } - - // Create two connections (the current max) to first server - nc1, _ := Connect(s1.ClientURL(), opts...) - defer nc1.Close() - nc1.Flush() - - nc2, _ := Connect(s1.ClientURL(), opts...) - defer nc2.Close() - nc2.Flush() - - if s1.NumClients() != 2 { - t.Fatalf("Expected 2 client connections to first server. Got %d", s1.NumClients()) - } - - if s2.NumClients() > 0 { - t.Fatalf("Expected 0 client connections to second server. Got %d", s2.NumClients()) - } - - // Kick one of our two server connections off first server. One client should reconnect to second server - newS1Opts := s1Opts - newS1Opts.MaxConn = 1 - err := s1.ReloadOptions(&newS1Opts) - if err != nil { - t.Fatalf("Unexpected error changing max_connections [%s]", err) - } - - select { - case err := <-errCh: - if err != ErrMaxConnectionsExceeded { - t.Fatalf("Unexpected error %v", err) - } - case <-time.After(2 * time.Second): - t.Fatal("Timed out waiting for disconnect event") - } - - select { - case <-reconnectCh: - case <-time.After(2 * time.Second): - t.Fatal("Timed out waiting for reconnect event") - } - - if s2.NumClients() <= 0 || s1.NumClients() > 1 { - t.Fatalf("Expected client reconnection to second server") - } -} - func TestSimplifiedURLs(t *testing.T) { for _, test := range []struct { name string @@ -1325,100 +1190,6 @@ func TestConnServers(t *testing.T) { validateURLs(c.Servers(), "nats://localhost:4333", "nats://localhost:4444") } -func TestConnAsyncCBDeadlock(t *testing.T) { - s := RunServerOnPort(TEST_PORT) - defer s.Shutdown() - - ch := make(chan bool) - o := GetDefaultOptions() - o.Url = fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) - o.ClosedCB = func(_ *Conn) { - ch <- true - } - o.AsyncErrorCB = func(nc *Conn, sub *Subscription, err error) { - // do something with nc that requires locking behind the scenes - _ = nc.LastError() - } - nc, err := o.Connect() - if err != nil { - t.Fatalf("Should have connected ok: %v", err) - } - - total := 300 - wg := &sync.WaitGroup{} - wg.Add(total) - for i := 0; i < total; i++ { - go func() { - // overwhelm asyncCB with errors - nc.processErr(AUTHORIZATION_ERR) - wg.Done() - }() - } - wg.Wait() - - nc.Close() - if e := Wait(ch); e != nil { - t.Fatal("Deadlock") - } -} - -func TestPingTimerLeakedOnClose(t *testing.T) { - s := RunServerOnPort(TEST_PORT) - defer s.Shutdown() - - nc, err := Connect(fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT)) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - nc.Close() - // There was a bug (issue #338) that if connection - // was created and closed quickly, the pinger would - // be created from a go-routine and would cause the - // connection object to be retained until the ping - // timer fired. - // Wait a little bit and check if the timer is set. - // With the defect it would be. - time.Sleep(100 * time.Millisecond) - nc.mu.Lock() - pingTimerSet := nc.ptmr != nil - nc.mu.Unlock() - if pingTimerSet { - t.Fatal("Pinger timer should not be set") - } -} - -func TestNoEcho(t *testing.T) { - s := RunServerOnPort(TEST_PORT) - defer s.Shutdown() - - url := fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) - - nc, err := Connect(url, NoEcho()) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer nc.Close() - - r := int32(0) - _, err = nc.Subscribe("foo", func(m *Msg) { - atomic.AddInt32(&r, 1) - }) - if err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - - err = nc.Publish("foo", []byte("Hello World")) - if err != nil { - t.Fatalf("Error on publish: %v", err) - } - nc.Flush() - nc.Flush() - - if nr := atomic.LoadInt32(&r); nr != 0 { - t.Fatalf("Expected no messages echoed back, received %d\n", nr) - } -} - func TestNoEchoOldServer(t *testing.T) { opts := GetDefaultOptions() opts.Url = DefaultURL @@ -1444,184 +1215,6 @@ func TestNoEchoOldServer(t *testing.T) { } } -// Trust Server Tests - -var ( - oSeed = []byte("SOAL7GTNI66CTVVNXBNQMG6V2HTDRWC3HGEP7D2OUTWNWSNYZDXWFOX4SU") - aSeed = []byte("SAAASUPRY3ONU4GJR7J5RUVYRUFZXG56F4WEXELLLORQ65AEPSMIFTOJGE") - uSeed = []byte("SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY") - - aJWT = "eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJLWjZIUVRXRlY3WkRZSFo3NklRNUhPM0pINDVRNUdJS0JNMzJTSENQVUJNNk5PNkU3TUhRIiwiaWF0IjoxNTQ0MDcxODg5LCJpc3MiOiJPRDJXMkk0TVZSQTVUR1pMWjJBRzZaSEdWTDNPVEtGV1FKRklYNFROQkVSMjNFNlA0NlMzNDVZWSIsInN1YiI6IkFBUFFKUVVQS1ZYR1c1Q1pINUcySEZKVUxZU0tERUxBWlJWV0pBMjZWRFpPN1dTQlVOSVlSRk5RIiwidHlwZSI6ImFjY291bnQiLCJuYXRzIjp7ImxpbWl0cyI6eyJzdWJzIjotMSwiY29ubiI6LTEsImltcG9ydHMiOi0xLCJleHBvcnRzIjotMSwiZGF0YSI6LTEsInBheWxvYWQiOi0xLCJ3aWxkY2FyZHMiOnRydWV9fX0.8o35JPQgvhgFT84Bi2Z-zAeSiLrzzEZn34sgr1DIBEDTwa-EEiMhvTeos9cvXxoZVCCadqZxAWVwS6paAMj8Bg" - - uJWT = "eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJBSFQzRzNXRElDS1FWQ1FUWFJUTldPRlVVUFRWNE00RFZQV0JGSFpJQUROWEZIWEpQR0FBIiwiaWF0IjoxNTQ0MDcxODg5LCJpc3MiOiJBQVBRSlFVUEtWWEdXNUNaSDVHMkhGSlVMWVNLREVMQVpSVldKQTI2VkRaTzdXU0JVTklZUkZOUSIsInN1YiI6IlVBVDZCV0NTQ1dMVUtKVDZLNk1CSkpPRU9UWFo1QUpET1lLTkVWUkZDN1ZOTzZPQTQzTjRUUk5PIiwidHlwZSI6InVzZXIiLCJuYXRzIjp7InB1YiI6e30sInN1YiI6e319fQ._8A1XM88Q2kp7XVJZ42bQuO9E3QPsNAGKtVjAkDycj8A5PtRPby9UpqBUZzBwiJQQO3TUcD5GGqSvsMm6X8hCQ" - - chained = ` ------BEGIN NATS USER JWT----- -eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJBSFQzRzNXRElDS1FWQ1FUWFJUTldPRlVVUFRWNE00RFZQV0JGSFpJQUROWEZIWEpQR0FBIiwiaWF0IjoxNTQ0MDcxODg5LCJpc3MiOiJBQVBRSlFVUEtWWEdXNUNaSDVHMkhGSlVMWVNLREVMQVpSVldKQTI2VkRaTzdXU0JVTklZUkZOUSIsInN1YiI6IlVBVDZCV0NTQ1dMVUtKVDZLNk1CSkpPRU9UWFo1QUpET1lLTkVWUkZDN1ZOTzZPQTQzTjRUUk5PIiwidHlwZSI6InVzZXIiLCJuYXRzIjp7InB1YiI6e30sInN1YiI6e319fQ._8A1XM88Q2kp7XVJZ42bQuO9E3QPsNAGKtVjAkDycj8A5PtRPby9UpqBUZzBwiJQQO3TUcD5GGqSvsMm6X8hCQ -------END NATS USER JWT------ - -************************* IMPORTANT ************************* -NKEY Seed printed below can be used to sign and prove identity. -NKEYs are sensitive and should be treated as secrets. - ------BEGIN USER NKEY SEED----- -SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY -------END USER NKEY SEED------ -` -) - -func runTrustServer() *server.Server { - kp, _ := nkeys.FromSeed(oSeed) - pub, _ := kp.PublicKey() - opts := natsserver.DefaultTestOptions - opts.Port = TEST_PORT - opts.TrustedKeys = []string{string(pub)} - s := RunServerWithOptions(&opts) - mr := &server.MemAccResolver{} - akp, _ := nkeys.FromSeed(aSeed) - apub, _ := akp.PublicKey() - mr.Store(string(apub), aJWT) - s.SetAccountResolver(mr) - return s -} - -func TestBasicUserJWTAuth(t *testing.T) { - if server.VERSION[0] == '1' { - t.Skip() - } - ts := runTrustServer() - defer ts.Shutdown() - - url := fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) - _, err := Connect(url) - if err == nil { - t.Fatalf("Expecting an error on connect") - } - - jwtCB := func() (string, error) { - return uJWT, nil - } - sigCB := func(nonce []byte) ([]byte, error) { - kp, _ := nkeys.FromSeed(uSeed) - sig, _ := kp.Sign(nonce) - return sig, nil - } - - // Try with user jwt but no sig - _, err = Connect(url, UserJWT(jwtCB, nil)) - if err == nil { - t.Fatalf("Expecting an error on connect") - } - - // Try with user callback - _, err = Connect(url, UserJWT(nil, sigCB)) - if err == nil { - t.Fatalf("Expecting an error on connect") - } - - nc, err := Connect(url, UserJWT(jwtCB, sigCB)) - if err != nil { - t.Fatalf("Expected to connect, got %v", err) - } - nc.Close() -} - -func TestUserCredentialsTwoFiles(t *testing.T) { - if server.VERSION[0] == '1' { - t.Skip() - } - ts := runTrustServer() - defer ts.Shutdown() - - userJWTFile := createTmpFile(t, []byte(uJWT)) - defer os.Remove(userJWTFile) - userSeedFile := createTmpFile(t, uSeed) - defer os.Remove(userSeedFile) - - url := fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) - nc, err := Connect(url, UserCredentials(userJWTFile, userSeedFile)) - if err != nil { - t.Fatalf("Expected to connect, got %v", err) - } - nc.Close() -} - -func TestUserCredentialsChainedFile(t *testing.T) { - if server.VERSION[0] == '1' { - t.Skip() - } - ts := runTrustServer() - defer ts.Shutdown() - - chainedFile := createTmpFile(t, []byte(chained)) - defer os.Remove(chainedFile) - - url := fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) - nc, err := Connect(url, UserCredentials(chainedFile)) - if err != nil { - t.Fatalf("Expected to connect, got %v", err) - } - nc.Close() - - chainedFile = createTmpFile(t, []byte("invalid content")) - defer os.Remove(chainedFile) - nc, err = Connect(url, UserCredentials(chainedFile)) - if err == nil || !strings.Contains(err.Error(), - "error signing nonce: unable to extract key pair from file") { - if nc != nil { - nc.Close() - } - t.Fatalf("Expected error about invalid creds file, got %q", err) - } -} - -func TestReconnectMissingCredentials(t *testing.T) { - ts := runTrustServer() - defer ts.Shutdown() - - chainedFile := createTmpFile(t, []byte(chained)) - defer os.Remove(chainedFile) - - url := fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) - errs := make(chan error, 1) - nc, err := Connect(url, UserCredentials(chainedFile), ErrorHandler(func(_ *Conn, _ *Subscription, err error) { - errs <- err - })) - if err != nil { - t.Fatalf("Expected to connect, got %v", err) - } - defer nc.Close() - os.Remove(chainedFile) - ts.Shutdown() - - ts = runTrustServer() - defer ts.Shutdown() - - select { - case err := <-errs: - if !strings.Contains(err.Error(), "no such file or directory") { - t.Fatalf("Expected error about missing creds file, got %q", err) - } - case <-time.After(5 * time.Second): - t.Fatal("Did not get error about missing creds file") - } -} - -func TestUserJWTAndSeed(t *testing.T) { - if server.VERSION[0] == '1' { - t.Skip() - } - ts := runTrustServer() - defer ts.Shutdown() - - url := fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) - nc, err := Connect(url, UserJWTAndSeed(uJWT, string(uSeed))) - if err != nil { - t.Fatalf("Expected to connect, got %v", err) - } - nc.Close() -} - func TestExpiredAuthentication(t *testing.T) { // The goal of these tests was to check how a client with an expiring JWT // behaves. It should receive an async -ERR indicating that the auth @@ -1778,125 +1371,6 @@ func TestExpiredAuthentication(t *testing.T) { } } -// If we are using TLS and have multiple servers we try to match the IP -// from a discovered server with the expected hostname for certs without IP -// designations. In certain cases where there is a not authorized error and -// we were trying the second server with the IP only and getting an error -// that was hard to understand for the end user. This did require -// Opts.Secure = false, but the fix removed the check on Opts.Secure to decide -// if we need to save off the hostname that we connected to first. -func TestUserCredentialsChainedFileNotFoundError(t *testing.T) { - if server.VERSION[0] == '1' { - t.Skip() - } - // Setup opts for both servers. - kp, _ := nkeys.FromSeed(oSeed) - pub, _ := kp.PublicKey() - opts := natsserver.DefaultTestOptions - opts.Port = -1 - opts.Cluster.Port = -1 - opts.TrustedKeys = []string{string(pub)} - tc := &server.TLSConfigOpts{ - CertFile: "./test/configs/certs/server_noip.pem", - KeyFile: "./test/configs/certs/key_noip.pem", - } - var err error - if opts.TLSConfig, err = server.GenTLSConfig(tc); err != nil { - panic("Can't build TLCConfig") - } - - // copy the opts for the second server. - opts2 := opts - - sa := RunServerWithOptions(&opts) - defer sa.Shutdown() - - routeAddr := fmt.Sprintf("nats-route://%s:%d", opts.Cluster.Host, opts.Cluster.Port) - rurl, _ := url.Parse(routeAddr) - opts2.Routes = []*url.URL{rurl} - - sb := RunServerWithOptions(&opts2) - defer sb.Shutdown() - - wait := time.Now().Add(2 * time.Second) - for time.Now().Before(wait) { - sanr := sa.NumRoutes() - sbnr := sb.NumRoutes() - if sanr == 1 && sbnr == 1 { - break - } - time.Sleep(50 * time.Millisecond) - } - - // Make sure we get the right error here. - nc, err := Connect(fmt.Sprintf("nats://localhost:%d", opts.Port), - RootCAs("./test/configs/certs/ca.pem"), - UserCredentials("filenotfound.creds")) - - if err == nil { - nc.Close() - t.Fatalf("Expected an error on missing credentials file") - } - if !strings.Contains(err.Error(), "no such file or directory") && - !strings.Contains(err.Error(), "The system cannot find the file specified") { - t.Fatalf("Expected a missing file error, got %q", err) - } -} - -func TestNkeyAuth(t *testing.T) { - if server.VERSION[0] == '1' { - t.Skip() - } - - seed := []byte("SUAKYRHVIOREXV7EUZTBHUHL7NUMHPMAS7QMDU3GTIUWEI5LDNOXD43IZY") - kp, _ := nkeys.FromSeed(seed) - pub, _ := kp.PublicKey() - - sopts := natsserver.DefaultTestOptions - sopts.Port = TEST_PORT - sopts.Nkeys = []*server.NkeyUser{{Nkey: string(pub)}} - ts := RunServerWithOptions(&sopts) - defer ts.Shutdown() - - opts := reconnectOpts - if _, err := opts.Connect(); err == nil { - t.Fatalf("Expected to fail with no nkey auth defined") - } - opts.Nkey = string(pub) - if _, err := opts.Connect(); err != ErrNkeyButNoSigCB { - t.Fatalf("Expected to fail with nkey defined but no signature callback, got %v", err) - } - badSign := func(nonce []byte) ([]byte, error) { - return []byte("VALID?"), nil - } - opts.SignatureCB = badSign - if _, err := opts.Connect(); err == nil { - t.Fatalf("Expected to fail with nkey and bad signature callback") - } - goodSign := func(nonce []byte) ([]byte, error) { - sig, err := kp.Sign(nonce) - if err != nil { - t.Fatalf("Failed signing nonce: %v", err) - } - return sig, nil - } - opts.SignatureCB = goodSign - nc, err := opts.Connect() - if err != nil { - t.Fatalf("Expected to succeed but got %v", err) - } - defer nc.Close() - - // Now disconnect by killing the server and restarting. - ts.Shutdown() - ts = RunServerWithOptions(&sopts) - defer ts.Shutdown() - - if err := nc.FlushTimeout(5 * time.Second); err != nil { - t.Fatalf("Error on Flush: %v", err) - } -} - func createTmpFile(t *testing.T, content []byte) string { t.Helper() conf, err := os.CreateTemp("", "") @@ -2008,465 +1482,6 @@ func TestNKeyOptionFromSeed(t *testing.T) { checkErrChannel(t, errCh) } -func TestLookupHostResultIsRandomized(t *testing.T) { - orgAddrs, err := net.LookupHost("localhost") - if err != nil { - t.Fatalf("Error looking up host: %v", err) - } - - // We actually want the IPv4 and IPv6 addresses, so lets make sure. - if !reflect.DeepEqual(orgAddrs, []string{"::1", "127.0.0.1"}) { - t.Skip("Was looking for IPv4 and IPv6 addresses for localhost to perform test") - } - - opts := natsserver.DefaultTestOptions - opts.Host = "127.0.0.1" - opts.Port = TEST_PORT - s1 := RunServerWithOptions(&opts) - defer s1.Shutdown() - - opts.Host = "::1" - s2 := RunServerWithOptions(&opts) - defer s2.Shutdown() - - for i := 0; i < 100; i++ { - nc, err := Connect(fmt.Sprintf("localhost:%d", TEST_PORT)) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer nc.Close() - } - - if ncls := s1.NumClients(); ncls < 35 || ncls > 65 { - t.Fatalf("Does not seem balanced between multiple servers: s1:%d, s2:%d", s1.NumClients(), s2.NumClients()) - } -} - -func TestLookupHostResultIsNotRandomizedWithNoRandom(t *testing.T) { - orgAddrs, err := net.LookupHost("localhost") - if err != nil { - t.Fatalf("Error looking up host: %v", err) - } - - // We actually want the IPv4 and IPv6 addresses, so lets make sure. - if !reflect.DeepEqual(orgAddrs, []string{"::1", "127.0.0.1"}) { - t.Skip("Was looking for IPv4 and IPv6 addresses for localhost to perform test") - } - - opts := natsserver.DefaultTestOptions - opts.Host = orgAddrs[0] - opts.Port = TEST_PORT - s1 := RunServerWithOptions(&opts) - defer s1.Shutdown() - - opts.Host = orgAddrs[1] - s2 := RunServerWithOptions(&opts) - defer s2.Shutdown() - - for i := 0; i < 100; i++ { - nc, err := Connect(fmt.Sprintf("localhost:%d", TEST_PORT), DontRandomize()) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer nc.Close() - } - - if ncls := s1.NumClients(); ncls != 100 { - t.Fatalf("Expected all clients on first server, only got %d of 100", ncls) - } -} - -func TestConnectedAddr(t *testing.T) { - s := RunServerOnPort(TEST_PORT) - defer s.Shutdown() - - var nc *Conn - if addr := nc.ConnectedAddr(); addr != _EMPTY_ { - t.Fatalf("Expected empty result for nil connection, got %q", addr) - } - nc, err := Connect(fmt.Sprintf("localhost:%d", TEST_PORT)) - if err != nil { - t.Fatalf("Error connecting: %v", err) - } - expected := s.Addr().String() - if addr := nc.ConnectedAddr(); addr != expected { - t.Fatalf("Expected address %q, got %q", expected, addr) - } - nc.Close() - if addr := nc.ConnectedAddr(); addr != _EMPTY_ { - t.Fatalf("Expected empty result for closed connection, got %q", addr) - } -} - -func TestSubscribeSyncRace(t *testing.T) { - s := RunServerOnPort(TEST_PORT) - defer s.Shutdown() - - nc, err := Connect(fmt.Sprintf("127.0.0.1:%d", TEST_PORT)) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer nc.Close() - - go func() { - time.Sleep(time.Millisecond) - nc.Close() - }() - - subj := "foo.sync.race" - for i := 0; i < 10000; i++ { - if _, err := nc.SubscribeSync(subj); err != nil { - break - } - if _, err := nc.QueueSubscribeSync(subj, "gc"); err != nil { - break - } - } -} - -func TestBadSubjectsAndQueueNames(t *testing.T) { - s := RunServerOnPort(TEST_PORT) - defer s.Shutdown() - - nc, err := Connect(fmt.Sprintf("127.0.0.1:%d", TEST_PORT)) - if err != nil { - t.Fatalf("Error connecting: %v", err) - } - defer nc.Close() - - // Make sure we get errors on bad subjects (spaces, etc) - // We want the client to protect the user. - badSubs := []string{"foo bar", "foo..bar", ".foo", "bar.baz.", "baz\t.foo"} - for _, subj := range badSubs { - if _, err := nc.SubscribeSync(subj); err != ErrBadSubject { - t.Fatalf("Expected an error of ErrBadSubject for %q, got %v", subj, err) - } - } - - badQueues := []string{"foo group", "group\t1", "g1\r\n2"} - for _, q := range badQueues { - if _, err := nc.QueueSubscribeSync("foo", q); err != ErrBadQueueName { - t.Fatalf("Expected an error of ErrBadQueueName for %q, got %v", q, err) - } - } -} - -func BenchmarkNextMsgNoTimeout(b *testing.B) { - s := RunServerOnPort(TEST_PORT) - defer s.Shutdown() - - ncp, err := Connect(fmt.Sprintf("127.0.0.1:%d", TEST_PORT)) - if err != nil { - b.Fatalf("Error connecting: %v", err) - } - ncs, err := Connect(fmt.Sprintf("127.0.0.1:%d", TEST_PORT), SyncQueueLen(b.N)) - if err != nil { - b.Fatalf("Error connecting: %v", err) - } - - // Test processing speed so no long subject or payloads. - subj := "a" - - sub, err := ncs.SubscribeSync(subj) - if err != nil { - b.Fatalf("Error subscribing: %v", err) - } - ncs.Flush() - - // Set it up so we can internally queue all the messages. - sub.SetPendingLimits(b.N, b.N*1000) - - for i := 0; i < b.N; i++ { - ncp.Publish(subj, nil) - } - ncp.Flush() - - // Wait for them to all be queued up, testing NextMsg not server here. - // Only wait at most one second. - wait := time.Now().Add(time.Second) - for time.Now().Before(wait) { - nm, _, err := sub.Pending() - if err != nil { - b.Fatalf("Error on Pending() - %v", err) - } - if nm >= b.N { - break - } - time.Sleep(10 * time.Millisecond) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - if _, err := sub.NextMsg(10 * time.Millisecond); err != nil { - b.Fatalf("Error getting message[%d]: %v", i, err) - } - } -} - -func TestAuthErrorOnReconnect(t *testing.T) { - // This is a bit of an artificial test, but it is to demonstrate - // that if the client is disconnected from a server (not due to an auth error), - // it will still correctly stop the reconnection logic if it gets twice an - // auth error from the same server. - - o1 := natsserver.DefaultTestOptions - o1.Port = -1 - s1 := RunServerWithOptions(&o1) - defer s1.Shutdown() - - o2 := natsserver.DefaultTestOptions - o2.Port = -1 - o2.Username = "ivan" - o2.Password = "pwd" - s2 := RunServerWithOptions(&o2) - defer s2.Shutdown() - - dch := make(chan bool) - cch := make(chan bool) - - urls := fmt.Sprintf("nats://%s:%d, nats://%s:%d", o1.Host, o1.Port, o2.Host, o2.Port) - nc, err := Connect(urls, - ReconnectWait(25*time.Millisecond), - ReconnectJitter(0, 0), - MaxReconnects(-1), - DontRandomize(), - ErrorHandler(func(_ *Conn, _ *Subscription, _ error) {}), - DisconnectErrHandler(func(_ *Conn, e error) { - dch <- true - }), - ClosedHandler(func(_ *Conn) { - cch <- true - })) - if err != nil { - t.Fatalf("Expected to connect, got err: %v\n", err) - } - defer nc.Close() - - s1.Shutdown() - - // wait for disconnect - if e := WaitTime(dch, 5*time.Second); e != nil { - t.Fatal("Did not receive a disconnect callback message") - } - - // Wait for ClosedCB - if e := WaitTime(cch, 5*time.Second); e != nil { - reconnects := nc.Stats().Reconnects - t.Fatalf("Did not receive a closed callback message, #reconnects: %v", reconnects) - } - - // We should have stopped after 2 reconnects. - if reconnects := nc.Stats().Reconnects; reconnects != 2 { - t.Fatalf("Expected 2 reconnects, got %v", reconnects) - } - - // Expect connection to be closed... - if !nc.IsClosed() { - t.Fatalf("Wrong status: %d\n", nc.Status()) - } -} - -func TestStatsRace(t *testing.T) { - o := natsserver.DefaultTestOptions - o.Port = -1 - s := RunServerWithOptions(&o) - defer s.Shutdown() - - nc, err := Connect(fmt.Sprintf("nats://%s:%d", o.Host, o.Port)) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer nc.Close() - - wg := sync.WaitGroup{} - wg.Add(1) - ch := make(chan bool) - go func() { - defer wg.Done() - for { - select { - case <-ch: - return - default: - nc.Stats() - } - } - }() - - nc.Subscribe("foo", func(_ *Msg) {}) - for i := 0; i < 1000; i++ { - nc.Publish("foo", []byte("hello")) - } - - close(ch) - wg.Wait() -} - -func TestRequestLeaksMapEntries(t *testing.T) { - o := natsserver.DefaultTestOptions - o.Port = -1 - s := RunServerWithOptions(&o) - defer s.Shutdown() - - nc, err := Connect(fmt.Sprintf("nats://%s:%d", o.Host, o.Port)) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer nc.Close() - - response := []byte("I will help you") - nc.Subscribe("foo", func(m *Msg) { - nc.Publish(m.Reply, response) - }) - - for i := 0; i < 100; i++ { - msg, err := nc.Request("foo", nil, 500*time.Millisecond) - if err != nil { - t.Fatalf("Received an error on Request test: %s", err) - } - if !bytes.Equal(msg.Data, response) { - t.Fatalf("Received invalid response") - } - } - nc.mu.Lock() - num := len(nc.respMap) - nc.mu.Unlock() - if num != 0 { - t.Fatalf("Expected 0 entries in response map, got %d", num) - } -} - -func TestRequestMultipleReplies(t *testing.T) { - o := natsserver.DefaultTestOptions - o.Port = -1 - s := RunServerWithOptions(&o) - defer s.Shutdown() - - nc, err := Connect(fmt.Sprintf("nats://%s:%d", o.Host, o.Port)) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer nc.Close() - - response := []byte("I will help you") - nc.Subscribe("foo", func(m *Msg) { - m.Respond(response) - m.Respond(response) - }) - nc.Flush() - - nc2, err := Connect(fmt.Sprintf("nats://%s:%d", o.Host, o.Port)) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer nc2.Close() - - errCh := make(chan error, 1) - // Send a request on bar and expect nothing - go func() { - if m, err := nc2.Request("bar", nil, 500*time.Millisecond); m != nil || err == nil { - errCh <- fmt.Errorf("Expected no reply, got m=%+v err=%v", m, err) - return - } - errCh <- nil - }() - - // Send a request on foo, we use only one of the 2 replies - if _, err := nc2.Request("foo", nil, time.Second); err != nil { - t.Fatalf("Received an error on Request test: %s", err) - } - if e := <-errCh; e != nil { - t.Fatal(e.Error()) - } -} - -func TestRequestInit(t *testing.T) { - o := natsserver.DefaultTestOptions - o.Port = -1 - s := RunServerWithOptions(&o) - defer s.Shutdown() - - nc, err := Connect(s.ClientURL()) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer nc.Close() - - if _, err := nc.Subscribe("foo", func(m *Msg) { - m.Respond([]byte("reply")) - }); err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - - // Artificially change the status to something that would make the internal subscribe - // call fail. Don't use CLOSED because then there is a risk that the flusher() goes away - // and so the rest of the test would fail. - nc.mu.Lock() - orgStatus := nc.status - nc.status = DRAINING_SUBS - nc.mu.Unlock() - - if _, err := nc.Request("foo", []byte("request"), 50*time.Millisecond); err == nil { - t.Fatal("Expected error, got none") - } - - nc.mu.Lock() - nc.status = orgStatus - nc.mu.Unlock() - - if _, err := nc.Request("foo", []byte("request"), 500*time.Millisecond); err != nil { - t.Fatalf("Error on request: %v", err) - } -} - -func TestGetRTT(t *testing.T) { - s := RunServerOnPort(-1) - defer s.Shutdown() - - nc, err := Connect(s.ClientURL(), ReconnectWait(10*time.Millisecond), ReconnectJitter(0, 0)) - if err != nil { - t.Fatalf("Expected to connect to server, got %v", err) - } - defer nc.Close() - - rtt, err := nc.RTT() - if err != nil { - t.Fatalf("Unexpected error getting RTT: %v", err) - } - if rtt > time.Second { - t.Fatalf("RTT value too large: %v", rtt) - } - // We should not get a value when in any disconnected state. - s.Shutdown() - time.Sleep(5 * time.Millisecond) - if _, err = nc.RTT(); err != ErrDisconnected { - t.Fatalf("Expected disconnected error getting RTT when disconnected, got %v", err) - } -} - -func TestGetClientIP(t *testing.T) { - s := RunServerOnPort(-1) - defer s.Shutdown() - - nc, err := Connect(s.ClientURL()) - if err != nil { - t.Fatalf("Expected to connect to server, got %v", err) - } - defer nc.Close() - - ip, err := nc.GetClientIP() - if err != nil { - t.Fatalf("Got error looking up IP: %v", err) - } - if !ip.IsLoopback() { - t.Fatalf("Expected a loopback IP, got %v", ip) - } - nc.Close() - if _, err := nc.GetClientIP(); err != ErrConnectionClosed { - t.Fatalf("Expected a connection closed error, got %v", err) - } -} - func TestNoPanicOnSrvPoolSizeChanging(t *testing.T) { listeners := []net.Listener{} ports := []int{} @@ -2544,132 +1559,6 @@ func TestNoPanicOnSrvPoolSizeChanging(t *testing.T) { wg.Wait() } -func TestReconnectWaitJitter(t *testing.T) { - s := RunServerOnPort(TEST_PORT) - defer s.Shutdown() - - rch := make(chan time.Time, 1) - nc, err := Connect(s.ClientURL(), - ReconnectWait(100*time.Millisecond), - ReconnectJitter(500*time.Millisecond, 0), - ReconnectHandler(func(_ *Conn) { - rch <- time.Now() - }), - ) - if err != nil { - t.Fatalf("Error during connect: %v", err) - } - defer nc.Close() - - s.Shutdown() - start := time.Now() - // Wait a bit so that the library tries a first time without waiting. - time.Sleep(50 * time.Millisecond) - s = RunServerOnPort(TEST_PORT) - defer s.Shutdown() - select { - case end := <-rch: - dur := end.Sub(start) - // We should wait at least the reconnect wait + random up to 500ms. - // Account for a bit of variation since we rely on the reconnect - // handler which is not invoked in place. - if dur < 90*time.Millisecond || dur > 800*time.Millisecond { - t.Fatalf("Wrong wait: %v", dur) - } - case <-time.After(5 * time.Second): - t.Fatalf("Should have reconnected") - } - nc.Close() - - // Use a long reconnect wait - nc, err = Connect(s.ClientURL(), ReconnectWait(10*time.Minute)) - if err != nil { - t.Fatalf("Error during connect: %v", err) - } - defer nc.Close() - - // Cause a disconnect - s.Shutdown() - // Wait a bit for the reconnect loop to go into wait mode. - time.Sleep(50 * time.Millisecond) - s = RunServerOnPort(TEST_PORT) - defer s.Shutdown() - // Now close and expect the reconnect go routine to return.. - nc.Close() - // Wait a bit to give a chance for the go routine to exit. - time.Sleep(50 * time.Millisecond) - buf := make([]byte, 100000) - n := runtime.Stack(buf, true) - if strings.Contains(string(buf[:n]), "doReconnect") { - t.Fatalf("doReconnect go routine still running:\n%s", buf[:n]) - } -} - -func TestCustomReconnectDelay(t *testing.T) { - s := RunServerOnPort(TEST_PORT) - defer s.Shutdown() - - expectedAttempt := 1 - errCh := make(chan error, 1) - cCh := make(chan bool, 1) - nc, err := Connect(s.ClientURL(), - Timeout(100*time.Millisecond), // Need to lower for Windows tests - CustomReconnectDelay(func(n int) time.Duration { - var err error - var delay time.Duration - if n != expectedAttempt { - err = fmt.Errorf("Expected attempt to be %v, got %v", expectedAttempt, n) - } else { - expectedAttempt++ - if n <= 4 { - delay = 100 * time.Millisecond - } - } - if err != nil { - select { - case errCh <- err: - default: - } - } - return delay - }), - MaxReconnects(4), - ClosedHandler(func(_ *Conn) { - cCh <- true - }), - ) - if err != nil { - t.Fatalf("Error during connect: %v", err) - } - defer nc.Close() - - // Cause disconnect - s.Shutdown() - - // We should be trying to reconnect 4 times - start := time.Now() - - // Wait on error or completion of test. - select { - case e := <-errCh: - if e != nil { - t.Fatal(e.Error()) - } - case <-cCh: - case <-time.After(2 * time.Second): - t.Fatalf("No CB invoked") - } - // On Windows, a failed connect attempt will last as much as Timeout(), - // so we need to take that into account. - max := 500 * time.Millisecond - if runtime.GOOS == "windows" { - max = time.Second - } - if dur := time.Since(start); dur >= max { - t.Fatalf("Waited too long on each reconnect: %v", dur) - } -} - func TestHeaderParser(t *testing.T) { shouldErr := func(hdr string) { t.Helper() @@ -2844,162 +1733,6 @@ func TestLameDuckMode(t *testing.T) { wg.Wait() } -func TestMsg_RespondMsg(t *testing.T) { - s := RunServerOnPort(-1) - defer s.Shutdown() - - nc, err := Connect(s.ClientURL()) - if err != nil { - t.Fatalf("Expected to connect to server, got %v", err) - } - defer nc.Close() - - sub, err := nc.SubscribeSync(NewInbox()) - if err != nil { - t.Fatalf("subscribe failed: %s", err) - } - - nc.PublishMsg(&Msg{Reply: sub.Subject, Subject: sub.Subject, Data: []byte("request")}) - req, err := sub.NextMsg(time.Second) - if err != nil { - t.Fatalf("NextMsg failed: %s", err) - } - - // verifies that RespondMsg sets the reply subject on msg based on req - err = req.RespondMsg(&Msg{Data: []byte("response")}) - if err != nil { - t.Fatalf("RespondMsg failed: %s", err) - } - - resp, err := sub.NextMsg(time.Second) - if err != nil { - t.Fatalf("NextMsg failed: %s", err) - } - - if !bytes.Equal(resp.Data, []byte("response")) { - t.Fatalf("did not get correct response: %q", resp.Data) - } -} - -func TestCustomInboxPrefix(t *testing.T) { - opts := &Options{} - for _, p := range []string{"$BOB.", "$BOB.*", "$BOB.>", ">", ".", "", "BOB.*.X", "BOB.>.X"} { - err := CustomInboxPrefix(p)(opts) - if err == nil { - t.Fatalf("Expected error for %q", p) - } - } - - s := RunServerOnPort(-1) - defer s.Shutdown() - - nc, err := Connect(s.ClientURL(), CustomInboxPrefix("$BOB")) - if err != nil { - t.Fatalf("Expected to connect to server, got %v", err) - } - defer nc.Close() - - sub, err := nc.Subscribe(NewInbox(), func(msg *Msg) { - if !strings.HasPrefix(msg.Reply, "$BOB.") { - t.Fatalf("invalid inbox subject %q received", msg.Reply) - } - - if len(strings.Split(msg.Reply, ".")) != 3 { - t.Fatalf("invalid number tokens in %s", msg.Reply) - } - - msg.Respond([]byte("ok")) - }) - if err != nil { - t.Fatalf("subscribe failed: %s", err) - } - - resp, err := nc.Request(sub.Subject, nil, time.Second) - if err != nil { - t.Fatalf("request failed: %s", err) - } - - if !bytes.Equal(resp.Data, []byte("ok")) { - t.Fatalf("did not receive ok: %q", resp.Data) - } -} - -func TestRespInbox(t *testing.T) { - s := RunServerOnPort(-1) - defer s.Shutdown() - - nc, err := Connect(s.ClientURL()) - if err != nil { - t.Fatalf("Expected to connect to server, got %v", err) - } - defer nc.Close() - - if _, err := nc.Subscribe("foo", func(msg *Msg) { - lastDot := strings.LastIndex(msg.Reply, ".") - if lastDot == -1 { - msg.Respond([]byte(fmt.Sprintf("Invalid reply subject: %q", msg.Reply))) - return - } - lastToken := msg.Reply[lastDot+1:] - if len(lastToken) != replySuffixLen { - msg.Respond([]byte(fmt.Sprintf("Invalid last token: %q", lastToken))) - return - } - msg.Respond(nil) - }); err != nil { - t.Fatalf("subscribe failed: %s", err) - } - resp, err := nc.Request("foo", []byte("check inbox"), time.Second) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - if len(resp.Data) > 0 { - t.Fatalf("Error: %s", resp.Data) - } -} - -func TestInProcessConn(t *testing.T) { - s := RunServerOnPort(-1) - defer s.Shutdown() - - nc, err := Connect("", InProcessServer(s)) - if err != nil { - t.Fatal(err) - } - - defer nc.Close() - - // Status should be connected. - if nc.Status() != CONNECTED { - t.Fatal("should be status CONNECTED") - } - - // The server should respond to a request. - if _, err := nc.RTT(); err != nil { - t.Fatal(err) - } -} - -func TestServerListWithTrailingComma(t *testing.T) { - s := RunServerOnPort(-1) - defer s.Shutdown() - - // Notice the comma at the end of the "list" - nc, err := Connect(fmt.Sprintf("%s,", s.ClientURL())) - if err != nil { - t.Fatalf("Unable to connect: %v", err) - } - defer nc.Close() - - // Now check server pool - nc.mu.Lock() - l := len(nc.srvPool) - nc.mu.Unlock() - if l != 1 { - t.Fatalf("There should be only 1 URL in the list, got %v", l) - } -} - func BenchmarkHeaderDecode(b *testing.B) { benchmarks := []struct { name string diff --git a/norace_test.go b/norace_test.go deleted file mode 100644 index 0edbf0d5b..000000000 --- a/norace_test.go +++ /dev/null @@ -1,767 +0,0 @@ -// Copyright 2019-2022 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !race && !skip_no_race_tests -// +build !race,!skip_no_race_tests - -package nats - -import ( - "context" - "fmt" - "os" - "strings" - "testing" - "time" -) - -func TestNoRaceParseStateReconnectFunctionality(t *testing.T) { - ts := RunServerOnPort(TEST_PORT) - ch := make(chan bool) - - opts := reconnectOpts - dch := make(chan bool) - dErrCh := make(chan bool) - opts.DisconnectedErrCB = func(_ *Conn, _ error) { - dErrCh <- true - } - opts.DisconnectedCB = func(_ *Conn) { - dch <- true - } - opts.NoCallbacksAfterClientClose = true - - nc, errc := opts.Connect() - if errc != nil { - t.Fatalf("Failed to create a connection: %v\n", errc) - } - ec, errec := NewEncodedConn(nc, DEFAULT_ENCODER) - if errec != nil { - nc.Close() - t.Fatalf("Failed to create an encoded connection: %v\n", errec) - } - defer ec.Close() - - testString := "bar" - ec.Subscribe("foo", func(s string) { - if s != testString { - t.Fatal("String doesn't match") - } - ch <- true - }) - ec.Flush() - - // Simulate partialState, this needs to be cleared. - nc.mu.Lock() - nc.ps.state = OP_PON - nc.mu.Unlock() - - ts.Shutdown() - // server is stopped here... - - if err := Wait(dErrCh); err != nil { - t.Fatal("Did not get the DisconnectedErrCB") - } - - select { - case <-dch: - t.Fatal("Get the DEPRECATED DisconnectedCB while DisconnectedErrCB was set") - default: - } - - if err := ec.Publish("foo", testString); err != nil { - t.Fatalf("Failed to publish message: %v\n", err) - } - - ts = RunServerOnPort(TEST_PORT) - defer ts.Shutdown() - - if err := ec.FlushTimeout(5 * time.Second); err != nil { - t.Fatalf("Error on Flush: %v", err) - } - - if err := Wait(ch); err != nil { - t.Fatal("Did not receive our message") - } - - expectedReconnectCount := uint64(1) - reconnectedCount := ec.Conn.Stats().Reconnects - - if reconnectedCount != expectedReconnectCount { - t.Fatalf("Reconnect count incorrect: %d vs %d\n", - reconnectedCount, expectedReconnectCount) - } - nc.Close() -} - -func TestNoRaceJetStreamConsumerSlowConsumer(t *testing.T) { - // This test fails many times, need to look harder at the imbalance. - t.SkipNow() - - s := RunServerOnPort(-1) - defer shutdownJSServerAndRemoveStorage(t, s) - - if err := s.EnableJetStream(nil); err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - nc, js := jsClient(t, s) - defer nc.Close() - - var err error - - _, err = js.AddStream(&StreamConfig{ - Name: "PENDING_TEST", - Subjects: []string{"js.p"}, - Storage: MemoryStorage, - }) - if err != nil { - t.Fatalf("stream create failed: %v", err) - } - - // Override default handler for test. - nc.SetErrorHandler(func(_ *Conn, _ *Subscription, _ error) {}) - - // Queue up 1M small messages. - toSend := uint64(1000000) - for i := uint64(0); i < toSend; i++ { - nc.Publish("js.p", []byte("ok")) - } - nc.Flush() - - str, err := js.StreamInfo("PENDING_TEST") - if err != nil { - t.Fatal(err) - } - - if nm := str.State.Msgs; nm != toSend { - t.Fatalf("Expected to have stored all %d msgs, got only %d", toSend, nm) - } - - var received uint64 - done := make(chan bool, 1) - - js.Subscribe("js.p", func(m *Msg) { - received++ - if received >= toSend { - done <- true - } - meta, err := m.Metadata() - if err != nil { - t.Fatalf("could not get message metadata: %s", err) - } - if meta.Sequence.Stream != received { - t.Errorf("Missed a sequence, was expecting %d but got %d, last error: '%v'", received, meta.Sequence.Stream, nc.LastError()) - nc.Close() - } - m.Ack() - }) - - select { - case <-time.After(5 * time.Second): - t.Fatalf("Failed to get all %d messages, only got %d", toSend, received) - case <-done: - } -} - -func TestNoRaceJetStreamPushFlowControlHeartbeats_SubscribeSync(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - errHandler := ErrorHandler(func(c *Conn, sub *Subscription, err error) { - t.Logf("WARN: %s", err) - }) - - nc, js := jsClient(t, s, errHandler) - defer nc.Close() - - var err error - - _, err = js.AddStream(&StreamConfig{ - Name: "TEST", - Subjects: []string{"foo"}, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - // Burst and try to hit the flow control limit of the server. - const totalMsgs = 16536 - payload := strings.Repeat("A", 1024) - for i := 0; i < totalMsgs; i++ { - if _, err := js.Publish("foo", []byte(fmt.Sprintf("i:%d/", i)+payload)); err != nil { - t.Fatal(err) - } - } - - hbTimer := 100 * time.Millisecond - sub, err := js.SubscribeSync("foo", - AckWait(30*time.Second), - MaxDeliver(1), - EnableFlowControl(), - IdleHeartbeat(hbTimer), - ) - if err != nil { - t.Fatal(err) - } - defer sub.Unsubscribe() - - info, err := sub.ConsumerInfo() - if err != nil { - t.Fatal(err) - } - if !info.Config.FlowControl { - t.Fatal("Expected Flow Control to be enabled") - } - if info.Config.Heartbeat != hbTimer { - t.Errorf("Expected %v, got: %v", hbTimer, info.Config.Heartbeat) - } - - m, err := sub.NextMsg(1 * time.Second) - if err != nil { - t.Fatalf("Error getting next message: %v", err) - } - meta, err := m.Metadata() - if err != nil { - t.Fatal(err) - } - if meta.NumPending > totalMsgs { - t.Logf("WARN: More pending messages than expected (%v), got: %v", totalMsgs, meta.NumPending) - } - err = m.Ack() - if err != nil { - t.Fatal(err) - } - - recvd := 1 - timeout := time.Now().Add(10 * time.Second) - for time.Now().Before(timeout) { - m, err := sub.NextMsg(1 * time.Second) - if err != nil { - t.Fatalf("Error getting next message: %v", err) - } - if len(m.Data) == 0 { - t.Fatalf("Unexpected empty message: %+v", m) - } - - if err := m.AckSync(); err != nil { - t.Fatalf("Error on ack message: %v", err) - } - recvd++ - - if recvd == totalMsgs { - break - } - } - - t.Run("idle heartbeats", func(t *testing.T) { - // Delay to get a few heartbeats. - time.Sleep(4 * hbTimer) - - timeout = time.Now().Add(5 * time.Second) - for time.Now().Before(timeout) { - msg, err := sub.NextMsg(200 * time.Millisecond) - if err != nil { - if err == ErrTimeout { - // If timeout, ok to stop checking for the test. - break - } - t.Fatal(err) - } - if len(msg.Data) == 0 { - t.Fatalf("Unexpected empty message: %+v", m) - } - - recvd++ - meta, err := msg.Metadata() - if err != nil { - t.Fatal(err) - } - if meta.NumPending == 0 { - break - } - } - if recvd > totalMsgs { - t.Logf("WARN: Received more messages than expected (%v), got: %v", totalMsgs, recvd) - } - }) - - t.Run("with context", func(t *testing.T) { - sub, err := js.SubscribeSync("foo", - AckWait(30*time.Second), - Durable("bar"), - EnableFlowControl(), - IdleHeartbeat(hbTimer), - ) - if err != nil { - t.Fatal(err) - } - defer sub.Unsubscribe() - - info, err = sub.ConsumerInfo() - if err != nil { - t.Fatal(err) - } - if !info.Config.FlowControl { - t.Fatal("Expected Flow Control to be enabled") - } - - recvd = 0 - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - for { - select { - case <-ctx.Done(): - t.Fatal(ctx.Err()) - default: - } - - m, err := sub.NextMsgWithContext(ctx) - if err != nil { - t.Fatalf("Error getting next message: %v", err) - } - if len(m.Data) == 0 { - t.Fatalf("Unexpected empty message: %+v", m) - } - - if err := m.Ack(); err != nil { - t.Fatalf("Error on ack message: %v", err) - } - recvd++ - - if recvd >= totalMsgs { - break - } - } - - // Delay to get a few heartbeats. - time.Sleep(4 * hbTimer) - ctx, cancel = context.WithTimeout(context.Background(), time.Second) - defer cancel() - FOR_LOOP: - for { - select { - case <-ctx.Done(): - if ctx.Err() == context.DeadlineExceeded { - break FOR_LOOP - } - default: - } - - msg, err := sub.NextMsgWithContext(ctx) - if err != nil { - if err == context.DeadlineExceeded { - break - } - t.Fatal(err) - } - if len(msg.Data) == 0 { - t.Fatalf("Unexpected empty message: %+v", m) - } - recvd++ - meta, err := msg.Metadata() - if err != nil { - t.Fatal(err) - } - if meta.NumPending == 0 { - break - } - } - if recvd > totalMsgs { - t.Logf("WARN: Received more messages than expected (%v), got: %v", totalMsgs, recvd) - } - }) -} - -func TestNoRaceJetStreamPushFlowControlHeartbeats_SubscribeAsync(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - var err error - - _, err = js.AddStream(&StreamConfig{ - Name: "TEST", - Subjects: []string{"foo"}, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - // Burst and try to hit the flow control limit of the server. - const totalMsgs = 16536 - payload := strings.Repeat("A", 1024) - for i := 0; i < totalMsgs; i++ { - if _, err := js.Publish("foo", []byte(payload)); err != nil { - t.Fatal(err) - } - } - - recvd := make(chan *Msg, totalMsgs) - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - errCh := make(chan error) - hbTimer := 100 * time.Millisecond - sub, err := js.Subscribe("foo", func(msg *Msg) { - if len(msg.Data) == 0 { - errCh <- fmt.Errorf("Unexpected empty message: %+v", msg) - } - recvd <- msg - - if len(recvd) == totalMsgs { - cancel() - } - }, EnableFlowControl(), IdleHeartbeat(hbTimer)) - if err != nil { - t.Fatal(err) - } - defer sub.Unsubscribe() - - info, err := sub.ConsumerInfo() - if err != nil { - t.Fatal(err) - } - if !info.Config.FlowControl { - t.Fatal("Expected Flow Control to be enabled") - } - if info.Config.Heartbeat != hbTimer { - t.Errorf("Expected %v, got: %v", hbTimer, info.Config.Heartbeat) - } - - <-ctx.Done() - - got := len(recvd) - expected := totalMsgs - if got != expected { - t.Errorf("Expected %v, got: %v", expected, got) - } - - // Wait for a couple of heartbeats to arrive and confirm there is no error. - select { - case <-time.After(1 * time.Second): - case err := <-errCh: - t.Fatal(err) - } -} - -func TestNoRaceJetStreamPushFlowControlHeartbeats_ChanSubscribe(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - errHandler := ErrorHandler(func(c *Conn, sub *Subscription, err error) { - t.Logf("WARN: %s : %v", err, sub.Subject) - }) - - nc, js := jsClient(t, s, errHandler) - defer nc.Close() - - var err error - - _, err = js.AddStream(&StreamConfig{ - Name: "TEST", - Subjects: []string{"foo"}, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - // Burst and try to hit the flow control limit of the server. - const totalMsgs = 16536 - payload := strings.Repeat("A", 1024) - for i := 0; i < totalMsgs; i++ { - if _, err := js.Publish("foo", []byte(fmt.Sprintf("i:%d/", i)+payload)); err != nil { - t.Fatal(err) - } - } - - hbTimer := 100 * time.Millisecond - mch := make(chan *Msg, 16536) - sub, err := js.ChanSubscribe("foo", mch, - AckWait(30*time.Second), - MaxDeliver(1), - EnableFlowControl(), - IdleHeartbeat(hbTimer), - ) - if err != nil { - t.Fatal(err) - } - defer sub.Unsubscribe() - - info, err := sub.ConsumerInfo() - if err != nil { - t.Fatal(err) - } - if !info.Config.FlowControl { - t.Fatal("Expected Flow Control to be enabled") - } - if info.Config.Heartbeat != hbTimer { - t.Errorf("Expected %v, got: %v", hbTimer, info.Config.Heartbeat) - } - - getNextMsg := func(mch chan *Msg, timeout time.Duration) (*Msg, error) { - t.Helper() - select { - case m := <-mch: - return m, nil - case <-time.After(timeout): - return nil, ErrTimeout - } - } - - m, err := getNextMsg(mch, 1*time.Second) - if err != nil { - t.Fatalf("Error getting next message: %v", err) - } - meta, err := m.Metadata() - if err != nil { - t.Fatal(err) - } - if meta.NumPending > totalMsgs { - t.Logf("WARN: More pending messages than expected (%v), got: %v", totalMsgs, meta.NumPending) - } - err = m.Ack() - if err != nil { - t.Fatal(err) - } - - recvd := 1 - ctx, done := context.WithTimeout(context.Background(), 10*time.Second) - defer done() - -Loop: - for { - select { - case <-ctx.Done(): - break Loop - case m := <-mch: - if err != nil { - t.Fatalf("Error getting next message: %v", err) - } - if len(m.Data) == 0 { - t.Fatalf("Unexpected empty message: %+v", m) - } - - if err := m.Ack(); err != nil { - t.Fatalf("Error on ack message: %v", err) - } - recvd++ - - if recvd == totalMsgs { - done() - } - } - } - - t.Run("idle heartbeats", func(t *testing.T) { - // Delay to get a few heartbeats. - time.Sleep(4 * hbTimer) - - ctx, done := context.WithTimeout(context.Background(), 1*time.Second) - defer done() - Loop: - for { - select { - case <-ctx.Done(): - break Loop - case msg := <-mch: - if err != nil { - if err == ErrTimeout { - // If timeout, ok to stop checking for the test. - break Loop - } - t.Fatal(err) - } - if len(msg.Data) == 0 { - t.Fatalf("Unexpected empty message: %+v", m) - } - - recvd++ - meta, err := msg.Metadata() - if err != nil { - t.Fatal(err) - } - if meta.NumPending == 0 { - break Loop - } - } - } - if recvd > totalMsgs { - t.Logf("WARN: Received more messages than expected (%v), got: %v", totalMsgs, recvd) - } - }) -} - -func TestNoRaceJetStreamPushFlowControl_SubscribeAsyncAndChannel(t *testing.T) { - s := RunBasicJetStreamServer() - defer shutdownJSServerAndRemoveStorage(t, s) - - errCh := make(chan error) - errHandler := ErrorHandler(func(c *Conn, sub *Subscription, err error) { - errCh <- err - }) - nc, err := Connect(s.ClientURL(), errHandler) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - defer nc.Close() - - const totalMsgs = 10_000 - - js, err := nc.JetStream(PublishAsyncMaxPending(totalMsgs)) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - _, err = js.AddStream(&StreamConfig{ - Name: "TEST", - Subjects: []string{"foo"}, - }) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - go func() { - payload := strings.Repeat("O", 4096) - for i := 0; i < totalMsgs; i++ { - js.PublishAsync("foo", []byte(payload)) - } - }() - - // Small channel that blocks and then buffered channel that can deliver all - // messages without blocking. - recvd := make(chan *Msg, 64) - delivered := make(chan *Msg, totalMsgs) - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - - // Dispatch channel consumer - go func() { - for m := range recvd { - select { - case <-ctx.Done(): - return - default: - } - - delivered <- m - if len(delivered) == totalMsgs { - cancel() - } - } - }() - - sub, err := js.Subscribe("foo", func(msg *Msg) { - // Cause bottleneck by having channel block when full - // because of work taking long. - recvd <- msg - }, EnableFlowControl(), IdleHeartbeat(5*time.Second)) - - if err != nil { - t.Fatal(err) - } - defer sub.Unsubscribe() - - // Set this lower then normal to make sure we do not exceed bytes pending with FC turned on. - sub.SetPendingLimits(totalMsgs, 4*1024*1024) // This matches server window for flowcontrol. - - info, err := sub.ConsumerInfo() - if err != nil { - t.Fatal(err) - } - if !info.Config.FlowControl { - t.Fatal("Expected Flow Control to be enabled") - } - <-ctx.Done() - - got := len(delivered) - expected := totalMsgs - if got != expected { - t.Errorf("Expected %d messages, got: %d", expected, got) - } - - // Wait for a couple of heartbeats to arrive and confirm there is no error. - select { - case <-time.After(1 * time.Second): - case err := <-errCh: - t.Errorf("error handler: %v", err) - } -} - -func TestNoRaceJetStreamChanSubscribeStall(t *testing.T) { - conf := createConfFile(t, []byte(` - listen: 127.0.0.1:-1 - jetstream: enabled - no_auth_user: pc - accounts: { - JS: { - jetstream: enabled - users: [ {user: pc, password: foo} ] - }, - } - `)) - defer os.Remove(conf) - - s, _ := RunServerWithConfig(conf) - defer shutdownJSServerAndRemoveStorage(t, s) - - nc, js := jsClient(t, s) - defer nc.Close() - - var err error - - // Create a stream. - if _, err = js.AddStream(&StreamConfig{Name: "STALL"}); err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - _, err = js.StreamInfo("STALL") - if err != nil { - t.Fatalf("stream lookup failed: %v", err) - } - - msg := []byte(strings.Repeat("A", 512)) - toSend := 100_000 - for i := 0; i < toSend; i++ { - // Use plain NATS here for speed. - nc.Publish("STALL", msg) - } - nc.Flush() - - batch := 100 - msgs := make(chan *Msg, batch-2) - sub, err := js.ChanSubscribe("STALL", msgs, - Durable("dlc"), - EnableFlowControl(), - IdleHeartbeat(5*time.Second), - MaxAckPending(batch-2), - ) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - defer sub.Unsubscribe() - - for received := 0; received < toSend; { - select { - case m := <-msgs: - received++ - meta, _ := m.Metadata() - if meta.Sequence.Consumer != uint64(received) { - t.Fatalf("Missed something, wanted %d but got %d", received, meta.Sequence.Consumer) - } - m.Ack() - case <-time.After(time.Second): - t.Fatalf("Timeout waiting for messages, last received was %d", received) - } - } -} diff --git a/scripts/cov.sh b/scripts/cov.sh index 6c80ab9b9..80828cb16 100755 --- a/scripts/cov.sh +++ b/scripts/cov.sh @@ -4,7 +4,7 @@ rm -rf ./cov mkdir cov go test -modfile=go_test.mod --failfast -vet=off -v -covermode=atomic -coverprofile=./cov/nats.out . -tags=skip_no_race_tests -go test -modfile=go_test.mod --failfast -vet=off -v -covermode=atomic -coverprofile=./cov/test.out -coverpkg=github.com/nats-io/nats.go ./test -tags=skip_no_race_tests +go test -modfile=go_test.mod --failfast -vet=off -v -covermode=atomic -coverprofile=./cov/test.out -coverpkg=github.com/nats-io/nats.go ./test -tags=skip_no_race_tests,internal_testing go test -modfile=go_test.mod --failfast -vet=off -v -covermode=atomic -coverprofile=./cov/jetstream.out -coverpkg=github.com/nats-io/nats.go/jetstream ./jetstream/test -tags=skip_no_race_tests go test -modfile=go_test.mod --failfast -vet=off -v -covermode=atomic -coverprofile=./cov/builtin.out -coverpkg=github.com/nats-io/nats.go/encoders/builtin ./test -run EncBuiltin -tags=skip_no_race_tests go test -modfile=go_test.mod --failfast -vet=off -v -covermode=atomic -coverprofile=./cov/protobuf.out -coverpkg=github.com/nats-io/nats.go/encoders/protobuf ./test -run EncProto -tags=skip_no_race_tests diff --git a/test/auth_test.go b/test/auth_test.go index b0aa060fc..a55b51217 100644 --- a/test/auth_test.go +++ b/test/auth_test.go @@ -1,4 +1,4 @@ -// Copyright 2012-2020 The NATS Authors +// Copyright 2012-2023 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 @@ -32,7 +32,7 @@ func TestAuth(t *testing.T) { opts.Port = 8232 opts.Username = "derek" opts.Password = "foo" - s := RunServerWithOptions(opts) + s := RunServerWithOptions(&opts) defer s.Shutdown() _, err := nats.Connect("nats://127.0.0.1:8232") @@ -75,7 +75,7 @@ func TestAuthFailNoDisconnectErrCB(t *testing.T) { opts.Port = 8232 opts.Username = "derek" opts.Password = "foo" - s := RunServerWithOptions(opts) + s := RunServerWithOptions(&opts) defer s.Shutdown() copts := nats.GetDefaultOptions() @@ -108,7 +108,7 @@ func TestAuthFailAllowReconnect(t *testing.T) { opts2.Port = 23233 opts2.Username = "ivan" opts2.Password = "foo" - ts2 := RunServerWithOptions(opts2) + ts2 := RunServerWithOptions(&opts2) defer ts2.Shutdown() ts3 := RunServerOnPort(23234) @@ -172,7 +172,7 @@ func TestTokenHandlerReconnect(t *testing.T) { opts2.Port = 8233 secret := "S3Cr3T0k3n!" opts2.Authorization = secret - ts2 := RunServerWithOptions(opts2) + ts2 := RunServerWithOptions(&opts2) defer ts2.Shutdown() reconnectch := make(chan bool) @@ -225,7 +225,7 @@ func TestTokenAuth(t *testing.T) { opts.Port = 8232 secret := "S3Cr3T0k3n!" opts.Authorization = secret - s := RunServerWithOptions(opts) + s := RunServerWithOptions(&opts) defer s.Shutdown() _, err := nats.Connect("nats://127.0.0.1:8232") @@ -269,7 +269,7 @@ func TestTokenHandlerAuth(t *testing.T) { opts.Port = 8232 secret := "S3Cr3T0k3n!" opts.Authorization = secret - s := RunServerWithOptions(opts) + s := RunServerWithOptions(&opts) defer s.Shutdown() _, err := nats.Connect("nats://127.0.0.1:8232") @@ -320,7 +320,7 @@ func TestPermViolation(t *testing.T) { }, }, } - s := RunServerWithOptions(opts) + s := RunServerWithOptions(&opts) defer s.Shutdown() errCh := make(chan error, 2) diff --git a/test/cluster_test.go b/test/cluster_test.go index 5f7595f98..5aa276b77 100644 --- a/test/cluster_test.go +++ b/test/cluster_test.go @@ -177,10 +177,10 @@ func TestAuthServers(t *testing.T) { opts.Password = "foo" opts.Port = 1222 - as1 := RunServerWithOptions(opts) + as1 := RunServerWithOptions(&opts) defer as1.Shutdown() opts.Port = 1224 - as2 := RunServerWithOptions(opts) + as2 := RunServerWithOptions(&opts) defer as2.Shutdown() pservers := strings.Join(plainServers, ",") diff --git a/test/compat_test.go b/test/compat_test.go index dd77021f2..928787899 100644 --- a/test/compat_test.go +++ b/test/compat_test.go @@ -1,3 +1,16 @@ +// Copyright 2023 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + //go:build compat // +build compat diff --git a/test/conn_test.go b/test/conn_test.go index f4bdd877e..588acc222 100644 --- a/test/conn_test.go +++ b/test/conn_test.go @@ -2391,7 +2391,7 @@ func TestConnectWithSimplifiedURLs(t *testing.T) { opts.Username = "" opts.Password = "" // and restart the server - s = RunServerWithOptions(*opts) + s = RunServerWithOptions(opts) defer s.Shutdown() // Test again against a server that wants TLS and check @@ -2435,7 +2435,7 @@ func TestGetClientID(t *testing.T) { optsA.Cluster.Port = -1 optsA.Cluster.Name = "test" - srvA := RunServerWithOptions(optsA) + srvA := RunServerWithOptions(&optsA) defer srvA.Shutdown() ch := make(chan bool, 1) @@ -2466,7 +2466,7 @@ func TestGetClientID(t *testing.T) { optsB.Cluster.Name = "test" optsB.Routes = server.RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", srvA.ClusterAddr().Port)) - srvB := RunServerWithOptions(optsB) + srvB := RunServerWithOptions(&optsB) defer srvB.Shutdown() // Wait for the discovered callback to fire @@ -2699,7 +2699,7 @@ func TestRetryOnFailedConnect(t *testing.T) { o.Port = 4222 o.Username = "user" o.Password = "password" - s := RunServerWithOptions(o) + s := RunServerWithOptions(&o) defer s.Shutdown() select { @@ -2729,7 +2729,7 @@ func TestRetryOnFailedConnectWithTLSError(t *testing.T) { } opts.TLSTimeout = 0.0001 - s := RunServerWithOptions(opts) + s := RunServerWithOptions(&opts) defer s.Shutdown() ch := make(chan bool, 1) @@ -2752,7 +2752,7 @@ func TestRetryOnFailedConnectWithTLSError(t *testing.T) { // Replace tls timeout to a reasonable value. s.Shutdown() opts.TLSTimeout = 2.0 - s = RunServerWithOptions(opts) + s = RunServerWithOptions(&opts) defer s.Shutdown() select { diff --git a/test/enc_test.go b/test/enc_test.go index 1f82e60b0..e40abbf06 100644 --- a/test/enc_test.go +++ b/test/enc_test.go @@ -1,4 +1,4 @@ -// Copyright 2012-2019 The NATS Authors +// Copyright 2012-2023 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 @@ -15,15 +15,16 @@ package test import ( "bytes" + "fmt" "testing" "time" "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/encoders/builtin" + "github.com/nats-io/nats.go/encoders/protobuf" + "github.com/nats-io/nats.go/encoders/protobuf/testdata" ) -const TEST_PORT = 8168 - func NewDefaultEConn(t *testing.T) *nats.EncodedConn { ec, err := nats.NewEncodedConn(NewConnection(t, TEST_PORT), nats.DEFAULT_ENCODER) if err != nil { @@ -468,3 +469,287 @@ func TestEncDrainSupported(t *testing.T) { t.Fatalf("Expected no error calling Drain(), got %v", err) } } + +const ENC_TEST_PORT = 8268 + +var options = nats.Options{ + Url: fmt.Sprintf("nats://127.0.0.1:%d", ENC_TEST_PORT), + AllowReconnect: true, + MaxReconnect: 10, + ReconnectWait: 100 * time.Millisecond, + Timeout: nats.DefaultTimeout, +} + +func TestPublishErrorAfterSubscribeDecodeError(t *testing.T) { + ts := RunServerOnPort(ENC_TEST_PORT) + defer ts.Shutdown() + opts := options + nc, _ := opts.Connect() + defer nc.Close() + + // Override default handler for test. + nc.SetErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, _ error) {}) + + c, _ := nats.NewEncodedConn(nc, nats.JSON_ENCODER) + + //Test message type + type Message struct { + Message string + } + const testSubj = "test" + + c.Subscribe(testSubj, func(msg *Message) {}) + + // Publish invalid json to catch decode error in subscription callback + c.Publish(testSubj, `foo`) + c.Flush() + + // Next publish should be successful + if err := c.Publish(testSubj, Message{"2"}); err != nil { + t.Error("Fail to send correct json message after decode error in subscription") + } +} + +func TestPublishErrorAfterInvalidPublishMessage(t *testing.T) { + ts := RunServerOnPort(ENC_TEST_PORT) + defer ts.Shutdown() + opts := options + nc, _ := opts.Connect() + defer nc.Close() + c, _ := nats.NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER) + const testSubj = "test" + + c.Publish(testSubj, &testdata.Person{Name: "Anatolii"}) + + // Publish invalid protobuf message to catch decode error + c.Publish(testSubj, "foo") + + // Next publish with valid protobuf message should be successful + if err := c.Publish(testSubj, &testdata.Person{Name: "Anatolii"}); err != nil { + t.Error("Fail to send correct protobuf message after invalid message publishing", err) + } +} + +func TestVariousFailureConditions(t *testing.T) { + ts := RunServerOnPort(ENC_TEST_PORT) + defer ts.Shutdown() + + dch := make(chan bool) + + opts := options + opts.AsyncErrorCB = func(_ *nats.Conn, _ *nats.Subscription, e error) { + dch <- true + } + nc, _ := opts.Connect() + nc.Close() + + if _, err := nats.NewEncodedConn(nil, protobuf.PROTOBUF_ENCODER); err == nil { + t.Fatal("Expected an error") + } + + if _, err := nats.NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER); err == nil || err != nats.ErrConnectionClosed { + t.Fatalf("Wrong error: %v instead of %v", err, nats.ErrConnectionClosed) + } + + nc, _ = opts.Connect() + defer nc.Close() + + if _, err := nats.NewEncodedConn(nc, "foo"); err == nil { + t.Fatal("Expected an error") + } + + c, err := nats.NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER) + if err != nil { + t.Fatalf("Unable to create encoded connection: %v", err) + } + defer c.Close() + + if _, err := c.Subscribe("bar", func(subj, obj string) {}); err != nil { + t.Fatalf("Unable to create subscription: %v", err) + } + + if err := c.Publish("bar", &testdata.Person{Name: "Ivan"}); err != nil { + t.Fatalf("Unable to publish: %v", err) + } + + if err := Wait(dch); err != nil { + t.Fatal("Did not get the async error callback") + } + + if err := c.PublishRequest("foo", "bar", "foo"); err == nil { + t.Fatal("Expected an error") + } + + if err := c.Request("foo", "foo", nil, 2*time.Second); err == nil { + t.Fatal("Expected an error") + } + + nc.Close() + + if err := c.PublishRequest("foo", "bar", &testdata.Person{Name: "Ivan"}); err == nil { + t.Fatal("Expected an error") + } + + resp := &testdata.Person{} + if err := c.Request("foo", &testdata.Person{Name: "Ivan"}, resp, 2*time.Second); err == nil { + t.Fatal("Expected an error") + } + + if _, err := c.Subscribe("foo", nil); err == nil { + t.Fatal("Expected an error") + } + + if _, err := c.Subscribe("foo", func() {}); err == nil { + t.Fatal("Expected an error") + } + + func() { + defer func() { + if r := recover(); r == nil { + t.Fatal("Expected an error") + } + }() + if _, err := c.Subscribe("foo", "bar"); err == nil { + t.Fatal("Expected an error") + } + }() +} + +func TesEncodedConnRequest(t *testing.T) { + ts := RunServerOnPort(ENC_TEST_PORT) + defer ts.Shutdown() + + dch := make(chan bool) + + opts := options + nc, _ := opts.Connect() + defer nc.Close() + + c, err := nats.NewEncodedConn(nc, protobuf.PROTOBUF_ENCODER) + if err != nil { + t.Fatalf("Unable to create encoded connection: %v", err) + } + defer c.Close() + + sentName := "Ivan" + recvName := "Kozlovic" + + if _, err := c.Subscribe("foo", func(_, reply string, p *testdata.Person) { + if p.Name != sentName { + t.Fatalf("Got wrong name: %v instead of %v", p.Name, sentName) + } + c.Publish(reply, &testdata.Person{Name: recvName}) + dch <- true + }); err != nil { + t.Fatalf("Unable to create subscription: %v", err) + } + if _, err := c.Subscribe("foo", func(_ string, p *testdata.Person) { + if p.Name != sentName { + t.Fatalf("Got wrong name: %v instead of %v", p.Name, sentName) + } + dch <- true + }); err != nil { + t.Fatalf("Unable to create subscription: %v", err) + } + + if err := c.Publish("foo", &testdata.Person{Name: sentName}); err != nil { + t.Fatalf("Unable to publish: %v", err) + } + + if err := Wait(dch); err != nil { + t.Fatal("Did not get message") + } + if err := Wait(dch); err != nil { + t.Fatal("Did not get message") + } + + response := &testdata.Person{} + if err := c.Request("foo", &testdata.Person{Name: sentName}, response, 2*time.Second); err != nil { + t.Fatalf("Unable to publish: %v", err) + } + if response.Name != recvName { + t.Fatalf("Wrong response: %v instead of %v", response.Name, recvName) + } + + if err := Wait(dch); err != nil { + t.Fatal("Did not get message") + } + if err := Wait(dch); err != nil { + t.Fatal("Did not get message") + } + + c2, err := nats.NewEncodedConn(nc, nats.GOB_ENCODER) + if err != nil { + t.Fatalf("Unable to create encoded connection: %v", err) + } + defer c2.Close() + + if _, err := c2.QueueSubscribe("bar", "baz", func(m *nats.Msg) { + response := &nats.Msg{Subject: m.Reply, Data: []byte(recvName)} + c2.Conn.PublishMsg(response) + dch <- true + }); err != nil { + t.Fatalf("Unable to create subscription: %v", err) + } + + mReply := nats.Msg{} + if err := c2.Request("bar", &nats.Msg{Data: []byte(sentName)}, &mReply, 2*time.Second); err != nil { + t.Fatalf("Unable to send request: %v", err) + } + if string(mReply.Data) != recvName { + t.Fatalf("Wrong reply: %v instead of %v", string(mReply.Data), recvName) + } + + if err := Wait(dch); err != nil { + t.Fatal("Did not get message") + } + + if c.LastError() != nil { + t.Fatalf("Unexpected connection error: %v", c.LastError()) + } + if c2.LastError() != nil { + t.Fatalf("Unexpected connection error: %v", c2.LastError()) + } +} + +func TestRequestGOB(t *testing.T) { + ts := RunServerOnPort(ENC_TEST_PORT) + defer ts.Shutdown() + + type Request struct { + Name string + } + + type Person struct { + Name string + Age int + } + + nc, err := nats.Connect(options.Url) + if err != nil { + t.Fatalf("Could not connect: %v", err) + } + defer nc.Close() + + ec, err := nats.NewEncodedConn(nc, nats.GOB_ENCODER) + if err != nil { + t.Fatalf("Unable to create encoded connection: %v", err) + } + defer ec.Close() + + ec.QueueSubscribe("foo.request", "g", func(subject, reply string, r *Request) { + if r.Name != "meg" { + t.Fatalf("Expected request to be 'meg', got %q", r) + } + response := &Person{Name: "meg", Age: 21} + ec.Publish(reply, response) + }) + + reply := Person{} + if err := ec.Request("foo.request", &Request{Name: "meg"}, &reply, time.Second); err != nil { + t.Fatalf("Failed to receive response: %v", err) + } + if reply.Name != "meg" || reply.Age != 21 { + t.Fatalf("Did not receive proper response, %+v", reply) + } +} diff --git a/test/headers_test.go b/test/headers_test.go index baf13cd82..dd016d2a4 100644 --- a/test/headers_test.go +++ b/test/headers_test.go @@ -122,7 +122,7 @@ func TestRequestMsgRaceAsyncInfo(t *testing.T) { s1Opts.Cluster.Name = "CLUSTER" s1Opts.Cluster.Host = "127.0.0.1" s1Opts.Cluster.Port = -1 - s := natsserver.RunServer(&s1Opts) + s := RunServerWithOptions(&s1Opts) defer s.Shutdown() nc, err := nats.Connect(s.ClientURL()) @@ -163,7 +163,7 @@ func TestRequestMsgRaceAsyncInfo(t *testing.T) { s2Opts.Cluster.Port = -1 s2Opts.Routes = server.RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", s.ClusterAddr().Port)) for { - s := natsserver.RunServer(&s2Opts) + s := RunServerWithOptions(&s2Opts) s.Shutdown() select { case <-ch: @@ -196,7 +196,7 @@ func TestNoHeaderSupport(t *testing.T) { opts := natsserver.DefaultTestOptions opts.Port = -1 opts.NoHeaderSupport = true - s := RunServerWithOptions(opts) + s := RunServerWithOptions(&opts) defer s.Shutdown() nc, err := nats.Connect(s.ClientURL()) diff --git a/test/helper_test.go b/test/helper_test.go index 632af70e4..9c04a40f9 100644 --- a/test/helper_test.go +++ b/test/helper_test.go @@ -28,6 +28,8 @@ import ( natsserver "github.com/nats-io/nats-server/v2/test" ) +const TEST_PORT = 8368 + // So that we can pass tests and benchmarks... type tLogger interface { Fatalf(format string, args ...any) @@ -112,12 +114,12 @@ func RunServerOnPort(port int) *server.Server { opts := natsserver.DefaultTestOptions opts.Port = port opts.Cluster.Name = "testing" - return RunServerWithOptions(opts) + return RunServerWithOptions(&opts) } // RunServerWithOptions will run a server with the given options. -func RunServerWithOptions(opts server.Options) *server.Server { - return natsserver.RunServer(&opts) +func RunServerWithOptions(opts *server.Options) *server.Server { + return natsserver.RunServer(opts) } // RunServerWithConfig will run a server with the given configuration file. @@ -129,7 +131,7 @@ func RunBasicJetStreamServer() *server.Server { opts := natsserver.DefaultTestOptions opts.Port = -1 opts.JetStream = true - return RunServerWithOptions(opts) + return RunServerWithOptions(&opts) } func createConfFile(t *testing.T, content []byte) string { diff --git a/test/js_internal_test.go b/test/js_internal_test.go new file mode 100644 index 000000000..a3f45833e --- /dev/null +++ b/test/js_internal_test.go @@ -0,0 +1,459 @@ +// Copyright 2023 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//go:build internal_testing +// +build internal_testing + +package test + +import ( + "crypto/sha256" + "encoding/base64" + "fmt" + "math/rand" + "strings" + "sync/atomic" + "testing" + "time" + + "github.com/nats-io/nats.go" +) + +// Need access to internals for loss testing. +func TestJetStreamOrderedConsumer(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + var err error + _, err = js.AddStream(&nats.StreamConfig{ + Name: "OBJECT", + Subjects: []string{"a"}, + Storage: nats.MemoryStorage, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Will be used as start time to validate proper reset to sequence on retries. + startTime := time.Now() + + // Create a sample asset. + msg := make([]byte, 1024*1024) + rand.Read(msg) + msg = []byte(base64.StdEncoding.EncodeToString(msg)) + mlen, sum := len(msg), sha256.Sum256(msg) + + // Now send into the stream as chunks. + const chunkSize = 1024 + for i := 0; i < mlen; i += chunkSize { + var chunk []byte + if mlen-i <= chunkSize { + chunk = msg[i:] + } else { + chunk = msg[i : i+chunkSize] + } + msg := nats.NewMsg("a") + msg.Data = chunk + msg.Header.Set("data", "true") + js.PublishMsgAsync(msg) + } + js.PublishAsync("a", nil) // eof + + select { + case <-js.PublishAsyncComplete(): + case <-time.After(time.Second): + t.Fatalf("Did not receive completion signal") + } + + // Do some tests on simple misconfigurations first. + // For ordered delivery a couple of things need to be set properly. + // Can't be durable or have ack policy that is not ack none or max deliver set. + _, err = js.SubscribeSync("a", nats.OrderedConsumer(), nats.Durable("dlc")) + if err == nil || !strings.Contains(err.Error(), "ordered consumer") { + t.Fatalf("Expected an error, got %v", err) + } + + _, err = js.SubscribeSync("a", nats.OrderedConsumer(), nats.AckExplicit()) + if err == nil || !strings.Contains(err.Error(), "ordered consumer") { + t.Fatalf("Expected an error, got %v", err) + } + + _, err = js.SubscribeSync("a", nats.OrderedConsumer(), nats.MaxDeliver(10)) + if err == nil || !strings.Contains(err.Error(), "ordered consumer") { + t.Fatalf("Expected an error, got %v", err) + } + + _, err = js.SubscribeSync("a", nats.OrderedConsumer(), nats.DeliverSubject("some.subject")) + if err == nil || !strings.Contains(err.Error(), "ordered consumer") { + t.Fatalf("Expected an error, got %v", err) + } + + si, err := js.StreamInfo("OBJECT") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + testConsumer := func() { + t.Helper() + var received uint32 + var rmsg []byte + done := make(chan bool, 1) + + cb := func(m *nats.Msg) { + // Check for eof + if len(m.Data) == 0 { + done <- true + return + } + atomic.AddUint32(&received, 1) + rmsg = append(rmsg, m.Data...) + } + // OrderedConsumer does not need HB, it sets it on its own, but for test we override which is ok. + sub, err := js.Subscribe("a", cb, nats.OrderedConsumer(), nats.IdleHeartbeat(250*time.Millisecond), nats.StartTime(startTime)) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer sub.Unsubscribe() + + select { + case <-done: + if rsum := sha256.Sum256(rmsg); rsum != sum { + t.Fatalf("Objects do not match") + } + case <-time.After(5 * time.Second): + t.Fatalf("Did not receive all chunks, only %d of %d total", atomic.LoadUint32(&received), si.State.Msgs-1) + } + } + + testSyncConsumer := func() { + t.Helper() + var received int + var rmsg []byte + + // OrderedConsumer does not need HB, it sets it on its own, but for test we override which is ok. + sub, err := js.SubscribeSync("a", nats.OrderedConsumer(), nats.IdleHeartbeat(250*time.Millisecond), nats.StartTime(startTime)) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer sub.Unsubscribe() + + var done bool + expires := time.Now().Add(5 * time.Second) + for time.Now().Before(expires) { + m, err := sub.NextMsg(time.Second) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if len(m.Data) == 0 { + done = true + break + } + received++ + rmsg = append(rmsg, m.Data...) + } + if !done { + t.Fatalf("Did not receive all chunks, only %d of %d total", received, si.State.Msgs-1) + } + if rsum := sha256.Sum256(rmsg); rsum != sum { + t.Fatalf("Objects do not match") + } + } + + // Now run normal test. + testConsumer() + testSyncConsumer() + + // Now introduce some loss. + singleLoss := func(m *nats.Msg) *nats.Msg { + if rand.Intn(100) <= 10 && m.Header.Get("data") != "" { + nc.RemoveMsgFilter("a") + return nil + } + return m + } + nc.AddMsgFilter("a", singleLoss) + testConsumer() + nc.AddMsgFilter("a", singleLoss) + testSyncConsumer() + + multiLoss := func(m *nats.Msg) *nats.Msg { + if rand.Intn(100) <= 10 && m.Header.Get("data") != "" { + return nil + } + return m + } + nc.AddMsgFilter("a", multiLoss) + testConsumer() + testSyncConsumer() + + firstOnly := func(m *nats.Msg) *nats.Msg { + if meta, err := m.Metadata(); err == nil { + if meta.Sequence.Consumer == 1 { + nc.RemoveMsgFilter("a") + return nil + } + } + return m + } + nc.AddMsgFilter("a", firstOnly) + testConsumer() + nc.AddMsgFilter("a", firstOnly) + testSyncConsumer() + + lastOnly := func(m *nats.Msg) *nats.Msg { + if meta, err := m.Metadata(); err == nil { + if meta.Sequence.Stream >= si.State.LastSeq-1 { + nc.RemoveMsgFilter("a") + return nil + } + } + return m + } + nc.AddMsgFilter("a", lastOnly) + testConsumer() + nc.AddMsgFilter("a", lastOnly) + testSyncConsumer() +} + +func TestJetStreamOrderedConsumerWithAutoUnsub(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + var err error + + _, err = js.AddStream(&nats.StreamConfig{ + Name: "OBJECT", + Subjects: []string{"a"}, + Storage: nats.MemoryStorage, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + count := int32(0) + sub, err := js.Subscribe("a", func(m *nats.Msg) { + atomic.AddInt32(&count, 1) + }, nats.OrderedConsumer(), nats.IdleHeartbeat(250*time.Millisecond)) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Ask to auto-unsub after 10 messages. + sub.AutoUnsubscribe(10) + + // Set a message filter that will drop 1 message + dm := 0 + singleLoss := func(m *nats.Msg) *nats.Msg { + if m.Header.Get("data") != "" { + dm++ + if dm == 5 { + nc.RemoveMsgFilter("a") + return nil + } + } + return m + } + nc.AddMsgFilter("a", singleLoss) + + // Now produce 20 messages + for i := 0; i < 20; i++ { + msg := nats.NewMsg("a") + msg.Data = []byte(fmt.Sprintf("msg_%d", i+1)) + msg.Header.Set("data", "true") + js.PublishMsgAsync(msg) + } + + select { + case <-js.PublishAsyncComplete(): + case <-time.After(time.Second): + t.Fatalf("Did not receive completion signal") + } + + // Wait for the subscription to be marked as invalid + deadline := time.Now().Add(time.Second) + ok := false + for time.Now().Before(deadline) { + if !sub.IsValid() { + ok = true + break + } + } + if !ok { + t.Fatalf("Subscription still valid") + } + + // Wait a bit to make sure we are not receiving more than expected, + // and give a chance for the server to process the auto-unsub + // protocol. + time.Sleep(500 * time.Millisecond) + + if n := atomic.LoadInt32(&count); n != 10 { + t.Fatalf("Sub should have received only 10 messages, got %v", n) + } + + // Now capture the in msgs count for the connection + inMsgs := nc.Stats().InMsgs + + // Send one more message and this count should not increase if the + // server had properly processed the auto-unsub after the + // reset of the ordered consumer. Use a different connection + // to send. + nc2, js2 := jsClient(t, s) + defer nc2.Close() + + js2.Publish("a", []byte("should not be received")) + + newInMsgs := nc.Stats().InMsgs + if inMsgs != newInMsgs { + t.Fatal("Seems that AUTO-UNSUB was not properly handled") + } +} + +func TestJetStreamSubscribeReconnect(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + rch := make(chan struct{}, 1) + nc, err := nats.Connect(s.ClientURL(), + nats.ReconnectWait(50*time.Millisecond), + nats.ReconnectHandler(func(_ *nats.Conn) { + select { + case rch <- struct{}{}: + default: + } + })) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer nc.Close() + + js, err := nc.JetStream(nats.MaxWait(250 * time.Millisecond)) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Create the stream using our client API. + _, err = js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"foo"}, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + sub, err := js.SubscribeSync("foo", nats.Durable("bar")) + if err != nil { + t.Fatalf("Error on subscribe: %v", err) + } + + sendAndReceive := func(msgContent string) { + t.Helper() + var ok bool + var err error + for i := 0; i < 5; i++ { + if _, err = js.Publish("foo", []byte(msgContent)); err != nil { + time.Sleep(250 * time.Millisecond) + continue + } + ok = true + break + } + if !ok { + t.Fatalf("Error on publish: %v", err) + } + msg, err := sub.NextMsg(time.Second) + if err != nil { + t.Fatal("Did not get message") + } + if string(msg.Data) != msgContent { + t.Fatalf("Unexpected content: %q", msg.Data) + } + if err := msg.AckSync(); err != nil { + t.Fatalf("Error on ack: %v", err) + } + } + + sendAndReceive("msg1") + + // Cause a disconnect... + nc.CloseTCPConn() + + // Wait for reconnect + select { + case <-rch: + case <-time.After(time.Second): + t.Fatal("Did not reconnect") + } + + // Make sure we can send and receive the msg + sendAndReceive("msg2") +} + +func TestJetStreamFlowControlStalled(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + var err error + + _, err = js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"a"}, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if _, err := js.SubscribeSync("a", + nats.DeliverSubject("ds"), + nats.Durable("dur"), + nats.IdleHeartbeat(200*time.Millisecond), + nats.EnableFlowControl()); err != nil { + t.Fatalf("Error on subscribe: %v", err) + } + + // Drop all incoming FC control messages. + jsCtrlFC := 2 + fcLoss := func(m *nats.Msg) *nats.Msg { + if _, ctrlType := nats.IsJSControlMessage(m); ctrlType == jsCtrlFC { + return nil + } + return m + } + nc.AddMsgFilter("ds", fcLoss) + + // Have a subscription on the FC subject to make sure that the library + // respond to the requests for un-stall + checkSub, err := nc.SubscribeSync("$JS.FC.>") + if err != nil { + t.Fatalf("Error on sub: %v", err) + } + + // Publish bunch of messages. + payload := make([]byte, 100*1024) + for i := 0; i < 250; i++ { + nc.Publish("a", payload) + } + + // Now wait that we respond to a stalled FC + if _, err := checkSub.NextMsg(2 * time.Second); err != nil { + t.Fatal("Library did not send FC") + } +} diff --git a/test/js_test.go b/test/js_test.go index d7ac0936c..28076707b 100644 --- a/test/js_test.go +++ b/test/js_test.go @@ -16,6 +16,7 @@ package test import ( "context" "crypto/rand" + "encoding/json" "errors" "fmt" mrand "math/rand" @@ -67,7 +68,7 @@ func restartBasicJSServer(t *testing.T, s *server.Server) *server.Server { opts.StoreDir = s.JetStreamConfig().StoreDir s.Shutdown() s.WaitForShutdown() - return RunServerWithOptions(opts) + return RunServerWithOptions(&opts) } func TestJetStreamNotEnabled(t *testing.T) { @@ -6126,7 +6127,7 @@ type jsServer struct { func (srv *jsServer) Restart() { srv.restart.Lock() defer srv.restart.Unlock() - srv.Server = natsserver.RunServer(srv.myopts) + srv.Server = RunServerWithOptions(srv.myopts) } func setupJSClusterWithSize(t *testing.T, clusterName string, size int) []*jsServer { @@ -6187,11 +6188,11 @@ func setupJSClusterWithSize(t *testing.T, clusterName string, size int) []*jsSer for i, o := range opts { o.Routes = routesStr - nodes[i] = &jsServer{Server: natsserver.RunServer(o), myopts: o} + nodes[i] = &jsServer{Server: RunServerWithOptions(o), myopts: o} } } else { o := opts[0] - nodes[0] = &jsServer{Server: natsserver.RunServer(o), myopts: o} + nodes[0] = &jsServer{Server: RunServerWithOptions(o), myopts: o} } // Wait until JS is ready. @@ -6214,7 +6215,7 @@ func withJSServer(t *testing.T, tfn func(t *testing.T, srvs ...*jsServer)) { opts.JetStream = true opts.LameDuckDuration = 3 * time.Second opts.LameDuckGracePeriod = 2 * time.Second - s := &jsServer{Server: RunServerWithOptions(opts), myopts: &opts} + s := &jsServer{Server: RunServerWithOptions(&opts), myopts: &opts} defer shutdownJSServerAndRemoveStorage(t, s.Server) tfn(t, s) } @@ -9623,3 +9624,731 @@ func TestJetStreamSubscribeConsumerName(t *testing.T) { t.Fatalf("Expected: %v, got: %v", expectedSize, result) } } + +func TestJetStreamOrderedConsumerDeleteAssets(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + var err error + + // For capturing errors. + errCh := make(chan error, 1) + nc.SetErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) { + errCh <- err + }) + + // Create a sample asset. + mlen := 128 * 1024 + msg := make([]byte, mlen) + + createStream := func() { + t.Helper() + _, err = js.AddStream(&nats.StreamConfig{ + Name: "OBJECT", + Subjects: []string{"a"}, + Storage: nats.MemoryStorage, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Now send into the stream as chunks. + const chunkSize = 256 + for i := 0; i < mlen; i += chunkSize { + var chunk []byte + if mlen-i <= chunkSize { + chunk = msg[i:] + } else { + chunk = msg[i : i+chunkSize] + } + js.PublishAsync("a", chunk) + } + select { + case <-js.PublishAsyncComplete(): + case <-time.After(time.Second): + t.Fatalf("Did not receive completion signal") + } + } + + t.Run("remove stream, expect error", func(t *testing.T) { + createStream() + + sub, err := js.SubscribeSync("a", nats.OrderedConsumer(), nats.IdleHeartbeat(200*time.Millisecond)) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer sub.Unsubscribe() + + // Since we are sync we will be paused here due to flow control. + time.Sleep(100 * time.Millisecond) + // Now delete the asset and make sure we get an error. + if err := js.DeleteStream("OBJECT"); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + // Make sure we get an error. + select { + case err := <-errCh: + if !errors.Is(err, nats.ErrStreamNotFound) { + t.Fatalf("Got wrong error, wanted %v, got %v", nats.ErrStreamNotFound, err) + } + case <-time.After(time.Second): + t.Fatalf("Did not receive err message as expected") + } + }) + + t.Run("remove consumer, expect it to be recreated", func(t *testing.T) { + createStream() + + createConsSub, err := nc.SubscribeSync("$JS.API.CONSUMER.CREATE.OBJECT.*.a") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer createConsSub.Unsubscribe() + // Again here the IdleHeartbeat is not required, just overriding top shorten test time. + sub, err := js.SubscribeSync("a", nats.OrderedConsumer(), nats.IdleHeartbeat(200*time.Millisecond)) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer sub.Unsubscribe() + + createConsMsg, err := createConsSub.NextMsg(time.Second) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !strings.Contains(string(createConsMsg.Data), `"stream_name":"OBJECT"`) { + t.Fatalf("Invalid message on create consumer subject: %q", string(createConsMsg.Data)) + } + + time.Sleep(100 * time.Millisecond) + ci, err := sub.ConsumerInfo() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + consName := ci.Name + + if err := js.DeleteConsumer("OBJECT", consName); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + createConsMsg, err = createConsSub.NextMsg(time.Second) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !strings.Contains(string(createConsMsg.Data), `"stream_name":"OBJECT"`) { + t.Fatalf("Invalid message on create consumer subject: %q", string(createConsMsg.Data)) + } + + time.Sleep(100 * time.Millisecond) + ci, err = sub.ConsumerInfo() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + newConsName := ci.Name + if consName == newConsName { + t.Fatalf("Consumer should be recreated, but consumer name is the same") + } + }) +} + +// We want to make sure we do the right thing with lots of concurrent queue durable consumer requests. +// One should win and the others should share the delivery subject with the first one who wins. +func TestJetStreamConcurrentQueueDurablePushConsumers(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + var err error + + // Create stream. + _, err = js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"foo"}, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Now create 10 durables concurrently. + subs := make([]*nats.Subscription, 0, 10) + var wg sync.WaitGroup + mx := &sync.Mutex{} + + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + sub, _ := js.QueueSubscribeSync("foo", "bar") + mx.Lock() + subs = append(subs, sub) + mx.Unlock() + }() + } + // Wait for all the consumers. + wg.Wait() + + si, err := js.StreamInfo("TEST") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if si.State.Consumers != 1 { + t.Fatalf("Expected exactly one consumer, got %d", si.State.Consumers) + } + + // Now send some messages and make sure they are distributed. + total := 1000 + for i := 0; i < total; i++ { + js.Publish("foo", []byte("Hello")) + } + + timeout := time.Now().Add(2 * time.Second) + got := 0 + for time.Now().Before(timeout) { + got = 0 + for _, sub := range subs { + pending, _, _ := sub.Pending() + // If a single sub has the total, then probably something is not right. + if pending == total { + t.Fatalf("A single member should not have gotten all messages") + } + got += pending + } + if got == total { + // We are done! + return + } + } + t.Fatalf("Expected %v messages, got only %v", total, got) +} + +func TestJetStreamAckTokens(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + var err error + + // Create the stream using our client API. + _, err = js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"foo"}, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + sub, err := js.SubscribeSync("foo") + if err != nil { + t.Fatalf("Error on subscribe: %v", err) + } + + now := time.Now() + for _, test := range []struct { + name string + expected *nats.MsgMetadata + str string + end string + err bool + }{ + { + "valid token size but not js ack", + nil, + "1.2.3.4.5.6.7.8.9", + "", + true, + }, + { + "valid token size but not js ack", + nil, + "1.2.3.4.5.6.7.8.9.10.11.12", + "", + true, + }, + { + "invalid token size", + nil, + "$JS.ACK.3.4.5.6.7.8", + "", + true, + }, + { + "invalid token size", + nil, + "$JS.ACK.3.4.5.6.7.8.9.10", + "", + true, + }, + { + "v1 style", + &nats.MsgMetadata{ + Stream: "TEST", + Consumer: "cons", + NumDelivered: 1, + Sequence: nats.SequencePair{ + Stream: 2, + Consumer: 3, + }, + Timestamp: now, + NumPending: 4, + }, + "", + "", + false, + }, + { + "v2 style no domain with hash", + &nats.MsgMetadata{ + Stream: "TEST", + Consumer: "cons", + NumDelivered: 1, + Sequence: nats.SequencePair{ + Stream: 2, + Consumer: 3, + }, + Timestamp: now, + NumPending: 4, + }, + "_.ACCHASH.", + ".abcde", + false, + }, + { + "v2 style with domain and hash", + &nats.MsgMetadata{ + Domain: "HUB", + Stream: "TEST", + Consumer: "cons", + NumDelivered: 1, + Sequence: nats.SequencePair{ + Stream: 2, + Consumer: 3, + }, + Timestamp: now, + NumPending: 4, + }, + "HUB.ACCHASH.", + ".abcde", + false, + }, + { + "more than 12 tokens", + &nats.MsgMetadata{ + Domain: "HUB", + Stream: "TEST", + Consumer: "cons", + NumDelivered: 1, + Sequence: nats.SequencePair{ + Stream: 2, + Consumer: 3, + }, + Timestamp: now, + NumPending: 4, + }, + "HUB.ACCHASH.", + ".abcde.ghijk.lmnop", + false, + }, + } { + t.Run(test.name, func(t *testing.T) { + msg := nats.NewMsg("foo") + msg.Sub = sub + if test.err { + msg.Reply = test.str + } else { + msg.Reply = fmt.Sprintf("$JS.ACK.%sTEST.cons.1.2.3.%v.4%s", test.str, now.UnixNano(), test.end) + } + + meta, err := msg.Metadata() + if test.err { + if err == nil || meta != nil { + t.Fatalf("Expected error for content: %q, got meta=%+v err=%v", test.str, meta, err) + } + // Expected error, we are done + return + } + if err != nil { + t.Fatalf("Expected: %+v with reply: %q, got error %v", test.expected, msg.Reply, err) + } + if meta.Timestamp.UnixNano() != now.UnixNano() { + t.Fatalf("Timestamp is bad: %v vs %v", now.UnixNano(), meta.Timestamp.UnixNano()) + } + meta.Timestamp = time.Time{} + test.expected.Timestamp = time.Time{} + if !reflect.DeepEqual(test.expected, meta) { + t.Fatalf("Expected %+v, got %+v", test.expected, meta) + } + }) + } +} + +func TestJetStreamTracing(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, err := nats.Connect(s.ClientURL()) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer nc.Close() + + ctr := 0 + js, err := nc.JetStream(&nats.ClientTrace{ + RequestSent: func(subj string, payload []byte) { + ctr++ + if subj != "$JS.API.STREAM.CREATE.X" { + t.Fatalf("Expected sent trace to %s: got: %s", "$JS.API.STREAM.CREATE.X", subj) + } + }, + ResponseReceived: func(subj string, payload []byte, hdr nats.Header) { + ctr++ + if subj != "$JS.API.STREAM.CREATE.X" { + t.Fatalf("Expected received trace to %s: got: %s", "$JS.API.STREAM.CREATE.X", subj) + } + }, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + _, err = js.AddStream(&nats.StreamConfig{Name: "X"}) + if err != nil { + t.Fatalf("add stream failed: %s", err) + } + if ctr != 2 { + t.Fatalf("did not receive all trace events: %d", ctr) + } +} + +func TestJetStreamExpiredPullRequests(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + var err error + + _, err = js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"foo"}, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + sub, err := js.PullSubscribe("foo", "bar", nats.PullMaxWaiting(2)) + if err != nil { + t.Fatalf("Error on subscribe: %v", err) + } + // Make sure that we reject batch < 1 + if _, err := sub.Fetch(0); err == nil { + t.Fatal("Expected error, did not get one") + } + if _, err := sub.Fetch(-1); err == nil { + t.Fatal("Expected error, did not get one") + } + + // Send 2 fetch requests + for i := 0; i < 2; i++ { + if _, err = sub.Fetch(1, nats.MaxWait(15*time.Millisecond)); err == nil { + t.Fatalf("Expected error, got none") + } + } + // Wait before the above expire + time.Sleep(50 * time.Millisecond) + batches := []int{1, 10} + for _, bsz := range batches { + start := time.Now() + _, err = sub.Fetch(bsz, nats.MaxWait(250*time.Millisecond)) + dur := time.Since(start) + if err == nil || dur < 50*time.Millisecond { + t.Fatalf("Expected error and wait for 250ms, got err=%v and dur=%v", err, dur) + } + } +} + +func TestJetStreamSyncSubscribeWithMaxAckPending(t *testing.T) { + opts := natsserver.DefaultTestOptions + opts.Port = -1 + opts.JetStream = true + opts.JetStreamLimits.MaxAckPending = 123 + s := RunServerWithOptions(&opts) + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + if _, err := js.AddStream(&nats.StreamConfig{Name: "MAX_ACK_PENDING", Subjects: []string{"foo"}}); err != nil { + t.Fatalf("Error adding stream: %v", err) + } + + // By default, the sync subscription will be created with a MaxAckPending equal + // to the internal sync queue len, which is 64K. So that should error out + // and make sure we get the actual limit + + checkSub := func(pull bool) { + var sub *nats.Subscription + var err error + if pull { + _, err = js.PullSubscribe("foo", "bar") + } else { + _, err = js.SubscribeSync("foo") + } + if err == nil || !strings.Contains(err.Error(), "system limit of 123") { + t.Fatalf("Unexpected error: %v", err) + } + + // But it should work if we use MaxAckPending() with lower value + if pull { + sub, err = js.PullSubscribe("foo", "bar", nats.MaxAckPending(64)) + } else { + sub, err = js.SubscribeSync("foo", nats.MaxAckPending(64)) + } + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + sub.Unsubscribe() + } + checkSub(false) + checkSub(true) +} + +func TestJetStreamClusterPlacement(t *testing.T) { + // There used to be a test here that would not work because it would require + // all servers in the cluster to know about each other tags. So we will simply + // verify that if a stream is configured with placement and tags, the proper + // "stream create" request is sent. + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + sub, err := nc.SubscribeSync("$JS.API.STREAM.CREATE.TEST") + if err != nil { + t.Fatalf("Error on sub: %v", err) + } + js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Placement: &nats.Placement{ + Tags: []string{"my_tag"}, + }, + }) + msg, err := sub.NextMsg(time.Second) + if err != nil { + t.Fatalf("Error getting stream create request: %v", err) + } + var req nats.StreamConfig + if err := json.Unmarshal(msg.Data, &req); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + if req.Placement == nil { + t.Fatal("Expected placement, did not get it") + } + if n := len(req.Placement.Tags); n != 1 { + t.Fatalf("Expected 1 tag, got %v", n) + } + if v := req.Placement.Tags[0]; v != "my_tag" { + t.Fatalf("Unexpected tag: %q", v) + } +} + +func TestJetStreamConsumerMemoryStorage(t *testing.T) { + opts := natsserver.DefaultTestOptions + opts.Port = -1 + opts.JetStream = true + s := RunServerWithOptions(&opts) + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + if _, err := js.AddStream(&nats.StreamConfig{Name: "STR", Subjects: []string{"foo"}}); err != nil { + t.Fatalf("Error adding stream: %v", err) + } + + // Pull ephemeral consumer with memory storage. + sub, err := js.PullSubscribe("foo", "", nats.ConsumerMemoryStorage()) + if err != nil { + t.Fatalf("Error on subscribe: %v", err) + } + + consInfo, err := sub.ConsumerInfo() + if err != nil { + t.Fatalf("Error getting consumer info: %v", err) + } + + if !consInfo.Config.MemoryStorage { + t.Fatalf("Expected memory storage to be %v, got %+v", true, consInfo.Config.MemoryStorage) + } + + // Create a sync subscription with an in-memory ephemeral consumer. + sub, err = js.SubscribeSync("foo", nats.ConsumerMemoryStorage()) + if err != nil { + t.Fatalf("Error on subscribe: %v", err) + } + + consInfo, err = sub.ConsumerInfo() + if err != nil { + t.Fatalf("Error getting consumer info: %v", err) + } + + if !consInfo.Config.MemoryStorage { + t.Fatalf("Expected memory storage to be %v, got %+v", true, consInfo.Config.MemoryStorage) + } + + // Async subscription with an in-memory ephemeral consumer. + cb := func(msg *nats.Msg) {} + sub, err = js.Subscribe("foo", cb, nats.ConsumerMemoryStorage()) + if err != nil { + t.Fatalf("Error on subscribe: %v", err) + } + + consInfo, err = sub.ConsumerInfo() + if err != nil { + t.Fatalf("Error getting consumer info: %v", err) + } + + if !consInfo.Config.MemoryStorage { + t.Fatalf("Expected memory storage to be %v, got %+v", true, consInfo.Config.MemoryStorage) + } +} + +func TestJetStreamStreamInfoWithSubjectDetails(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + var err error + + _, err = js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"test.*"}, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Publish on enough subjects to exercise the pagination + payload := make([]byte, 10) + for i := 0; i < 100001; i++ { + _, err := js.Publish(fmt.Sprintf("test.%d", i), payload) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + } + + // Check that passing a filter returns the subject details + result, err := js.StreamInfo("TEST", &nats.StreamInfoRequest{SubjectsFilter: ">"}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if len(result.State.Subjects) != 100001 { + t.Fatalf("expected 100001 subjects in the stream, but got %d instead", len(result.State.Subjects)) + } + + // Check that passing no filter does not return any subject details + result, err = js.StreamInfo("TEST") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if len(result.State.Subjects) != 0 { + t.Fatalf("expected 0 subjects details from StreamInfo, but got %d instead", len(result.State.Subjects)) + } +} + +func TestStreamNameBySubject(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + var err error + + _, err = js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"test.*"}, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + for _, test := range []struct { + name string + streamName string + err error + }{ + + {name: "valid wildcard lookup", streamName: "test.*", err: nil}, + {name: "valid explicit lookup", streamName: "test.a", err: nil}, + {name: "lookup on not existing stream", streamName: "not.existing", err: nats.ErrNoMatchingStream}, + } { + + stream, err := js.StreamNameBySubject(test.streamName) + if err != test.err { + t.Fatalf("expected %v, got %v", test.err, err) + } + + if stream != "TEST" && err == nil { + t.Fatalf("returned stream name should be 'TEST'") + } + } +} + +func TestJetStreamTransform(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + _, err := js.AddStream(&nats.StreamConfig{ + Name: "ORIGIN", + Subjects: []string{"test"}, + SubjectTransform: &nats.SubjectTransformConfig{Source: ">", Destination: "transformed.>"}, + Storage: nats.MemoryStorage, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + err = nc.Publish("test", []byte("1")) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + _, err = js.AddStream(&nats.StreamConfig{ + Subjects: []string{}, + Name: "SOURCING", + Sources: []*nats.StreamSource{{Name: "ORIGIN", SubjectTransforms: []nats.SubjectTransformConfig{{Source: ">", Destination: "fromtest.>"}}}}, + Storage: nats.MemoryStorage, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Create a sync subscription with an in-memory ephemeral consumer. + sub, err := js.SubscribeSync("fromtest.>", nats.ConsumerMemoryStorage(), nats.BindStream("SOURCING")) + if err != nil { + t.Fatalf("Error on subscribe: %v", err) + } + + m, err := sub.NextMsg(time.Second) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if m.Subject != "fromtest.transformed.test" { + t.Fatalf("the subject of the message doesn't match the expected fromtest.transformed.test: %s", m.Subject) + } + +} diff --git a/test/kv_test.go b/test/kv_test.go index 026891aeb..afa937de5 100644 --- a/test/kv_test.go +++ b/test/kv_test.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022 The NATS Authors +// Copyright 2021-2023 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 @@ -15,6 +15,7 @@ package test import ( "context" + "errors" "fmt" "os" "reflect" @@ -1145,3 +1146,252 @@ func TestKeyValueMirrorCrossDomains(t *testing.T) { t.Fatalf("Got wrong value: %q vs %q", e.Value(), "ivan") } } + +func TestKeyValueNonDirectGet(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + _, err := js.CreateKeyValue(&nats.KeyValueConfig{Bucket: "TEST"}) + if err != nil { + t.Fatalf("Error creating store: %v", err) + } + si, err := js.StreamInfo("KV_TEST") + if err != nil { + t.Fatalf("Error getting stream info: %v", err) + } + if !si.Config.AllowDirect { + t.Fatal("Expected allow direct to be set, it was not") + } + + cfg := si.Config + cfg.AllowDirect = false + if _, err := js.UpdateStream(&cfg); err != nil { + t.Fatalf("Error updating stream: %v", err) + } + kvi, err := js.KeyValue("TEST") + if err != nil { + t.Fatalf("Error getting kv: %v", err) + } + + if _, err := kvi.PutString("key1", "val1"); err != nil { + t.Fatalf("Error putting key: %v", err) + } + if _, err := kvi.PutString("key2", "val2"); err != nil { + t.Fatalf("Error putting key: %v", err) + } + if v, err := kvi.Get("key2"); err != nil || string(v.Value()) != "val2" { + t.Fatalf("Error on get: v=%+v err=%v", v, err) + } + if v, err := kvi.GetRevision("key1", 1); err != nil || string(v.Value()) != "val1" { + t.Fatalf("Error on get revisiong: v=%+v err=%v", v, err) + } + if v, err := kvi.GetRevision("key1", 2); err == nil { + t.Fatalf("Expected error, got %+v", v) + } +} + +func TestKeyValueRePublish(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + if _, err := js.CreateKeyValue(&nats.KeyValueConfig{ + Bucket: "TEST_UPDATE", + }); err != nil { + t.Fatalf("Error creating store: %v", err) + } + // This is expected to fail since server does not support as of now + // the update of RePublish. + if _, err := js.CreateKeyValue(&nats.KeyValueConfig{ + Bucket: "TEST_UPDATE", + RePublish: &nats.RePublish{Source: ">", Destination: "bar.>"}, + }); err == nil { + t.Fatal("Expected failure, did not get one") + } + + kv, err := js.CreateKeyValue(&nats.KeyValueConfig{ + Bucket: "TEST", + RePublish: &nats.RePublish{Source: ">", Destination: "bar.>"}, + }) + if err != nil { + t.Fatalf("Error creating store: %v", err) + } + si, err := js.StreamInfo("KV_TEST") + if err != nil { + t.Fatalf("Error getting stream info: %v", err) + } + if si.Config.RePublish == nil { + t.Fatal("Expected republish to be set, it was not") + } + + sub, err := nc.SubscribeSync("bar.>") + if err != nil { + t.Fatalf("Error on sub: %v", err) + } + if _, err := kv.Put("foo", []byte("value")); err != nil { + t.Fatalf("Error on put: %v", err) + } + msg, err := sub.NextMsg(time.Second) + if err != nil { + t.Fatalf("Error on next: %v", err) + } + if v := string(msg.Data); v != "value" { + t.Fatalf("Unexpected value: %s", v) + } + // The message should also have a header with the actual subject + kvSubjectsPreTmpl := "$KV.%s." + expected := fmt.Sprintf(kvSubjectsPreTmpl, "TEST") + "foo" + if v := msg.Header.Get(nats.JSSubject); v != expected { + t.Fatalf("Expected subject header %q, got %q", expected, v) + } +} + +func TestKeyValueMirrorDirectGet(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + kv, err := js.CreateKeyValue(&nats.KeyValueConfig{Bucket: "TEST"}) + if err != nil { + t.Fatalf("Error creating kv: %v", err) + } + _, err = js.AddStream(&nats.StreamConfig{ + Name: "MIRROR", + Mirror: &nats.StreamSource{Name: "KV_TEST"}, + MirrorDirect: true, + }) + if err != nil { + t.Fatalf("Error creating mirror: %v", err) + } + + for i := 0; i < 100; i++ { + key := fmt.Sprintf("KEY.%d", i) + if _, err := kv.PutString(key, "42"); err != nil { + t.Fatalf("Error adding key: %v", err) + } + } + + // Make sure all gets work. + for i := 0; i < 100; i++ { + if _, err := kv.Get("KEY.22"); err != nil { + t.Fatalf("Got error getting key: %v", err) + } + } +} + +func TestKeyValueCreate(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + kv, err := js.CreateKeyValue(&nats.KeyValueConfig{Bucket: "TEST"}) + if err != nil { + t.Fatalf("Error creating kv: %v", err) + } + + _, err = kv.Create("key", []byte("1")) + if err != nil { + t.Fatalf("Error creating key: %v", err) + } + + _, err = kv.Create("key", []byte("1")) + expected := "nats: wrong last sequence: 1: key exists" + if err.Error() != expected { + t.Fatalf("Expected %q, got: %v", expected, err) + } + if !errors.Is(err, nats.ErrKeyExists) { + t.Fatalf("Expected ErrKeyExists, got: %v", err) + } + aerr := &nats.APIError{} + if !errors.As(err, &aerr) { + t.Fatalf("Expected APIError, got: %v", err) + } + if aerr.Description != "wrong last sequence: 1" { + t.Fatalf("Unexpected APIError message, got: %v", aerr.Description) + } + if aerr.ErrorCode != 10071 { + t.Fatalf("Unexpected error code, got: %v", aerr.ErrorCode) + } + if aerr.Code != nats.ErrKeyExists.APIError().Code { + t.Fatalf("Unexpected error code, got: %v", aerr.Code) + } + var kerr nats.JetStreamError + if !errors.As(err, &kerr) { + t.Fatalf("Expected KeyValueError, got: %v", err) + } + if kerr.APIError().ErrorCode != 10071 { + t.Fatalf("Unexpected error code, got: %v", kerr.APIError().ErrorCode) + } +} + +func TestKeyValueSourcing(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + kvA, err := js.CreateKeyValue(&nats.KeyValueConfig{Bucket: "A"}) + if err != nil { + t.Fatalf("Error creating kv: %v", err) + } + + _, err = kvA.Create("keyA", []byte("1")) + if err != nil { + t.Fatalf("Error creating key: %v", err) + } + + if _, err := kvA.Get("keyA"); err != nil { + t.Fatalf("Got error getting keyA from A: %v", err) + } + + kvB, err := js.CreateKeyValue(&nats.KeyValueConfig{Bucket: "B"}) + if err != nil { + t.Fatalf("Error creating kv: %v", err) + } + + _, err = kvB.Create("keyB", []byte("1")) + if err != nil { + t.Fatalf("Error creating key: %v", err) + } + + kvC, err := js.CreateKeyValue(&nats.KeyValueConfig{Bucket: "C", Sources: []*nats.StreamSource{{Name: "A"}, {Name: "B"}}}) + if err != nil { + t.Fatalf("Error creating kv: %v", err) + } + + // Wait half a second to make sure it has time to populate the stream from it's sources + i := 0 + for { + status, err := kvC.Status() + if err != nil { + t.Fatalf("Error getting bucket status: %v", err) + } + if status.Values() == 2 { + break + } else { + i++ + if i > 3 { + t.Fatalf("Error sourcing bucket does not contain the expected number of values") + } + } + time.Sleep(20 * time.Millisecond) + } + + if _, err := kvC.Get("keyA"); err != nil { + t.Fatalf("Got error getting keyA from C: %v", err) + } + + if _, err := kvC.Get("keyB"); err != nil { + t.Fatalf("Got error getting keyB from C: %v", err) + } +} diff --git a/test/nats_test.go b/test/nats_test.go new file mode 100644 index 000000000..91ed88ddd --- /dev/null +++ b/test/nats_test.go @@ -0,0 +1,1141 @@ +// Copyright 2023 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 ( + "bytes" + "fmt" + "net" + "net/url" + "os" + "reflect" + "runtime" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/nats-io/nats-server/v2/server" + natsserver "github.com/nats-io/nats-server/v2/test" + "github.com/nats-io/nats.go" + "github.com/nats-io/nkeys" +) + +func TestMaxConnectionsReconnect(t *testing.T) { + // Start first server + s1Opts := natsserver.DefaultTestOptions + s1Opts.Port = -1 + s1Opts.MaxConn = 2 + s1Opts.Cluster = server.ClusterOpts{Name: "test", Host: "127.0.0.1", Port: -1} + s1 := RunServerWithOptions(&s1Opts) + defer s1.Shutdown() + + // Start second server + s2Opts := natsserver.DefaultTestOptions + s2Opts.Port = -1 + s2Opts.MaxConn = 2 + s2Opts.Cluster = server.ClusterOpts{Name: "test", Host: "127.0.0.1", Port: -1} + s2Opts.Routes = server.RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", s1Opts.Cluster.Port)) + s2 := RunServerWithOptions(&s2Opts) + defer s2.Shutdown() + + errCh := make(chan error, 2) + reconnectCh := make(chan struct{}) + opts := []nats.Option{ + nats.MaxReconnects(2), + nats.ReconnectWait(10 * time.Millisecond), + nats.Timeout(200 * time.Millisecond), + nats.DisconnectErrHandler(func(_ *nats.Conn, err error) { + if err != nil { + errCh <- err + } + }), + nats.ReconnectHandler(func(_ *nats.Conn) { + reconnectCh <- struct{}{} + }), + } + + // Create two connections (the current max) to first server + nc1, _ := nats.Connect(s1.ClientURL(), opts...) + defer nc1.Close() + nc1.Flush() + + nc2, _ := nats.Connect(s1.ClientURL(), opts...) + defer nc2.Close() + nc2.Flush() + + if s1.NumClients() != 2 { + t.Fatalf("Expected 2 client connections to first server. Got %d", s1.NumClients()) + } + + if s2.NumClients() > 0 { + t.Fatalf("Expected 0 client connections to second server. Got %d", s2.NumClients()) + } + + // Kick one of our two server connections off first server. One client should reconnect to second server + newS1Opts := s1Opts + newS1Opts.MaxConn = 1 + err := s1.ReloadOptions(&newS1Opts) + if err != nil { + t.Fatalf("Unexpected error changing max_connections [%s]", err) + } + + select { + case err := <-errCh: + if err != nats.ErrMaxConnectionsExceeded { + t.Fatalf("Unexpected error %v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("Timed out waiting for disconnect event") + } + + select { + case <-reconnectCh: + case <-time.After(2 * time.Second): + t.Fatal("Timed out waiting for reconnect event") + } + + if s2.NumClients() <= 0 || s1.NumClients() > 1 { + t.Fatalf("Expected client reconnection to second server") + } +} + +func TestNoEcho(t *testing.T) { + s := RunServerOnPort(TEST_PORT) + defer s.Shutdown() + + url := fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) + + nc, err := nats.Connect(url, nats.NoEcho()) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + defer nc.Close() + + r := int32(0) + _, err = nc.Subscribe("foo", func(m *nats.Msg) { + atomic.AddInt32(&r, 1) + }) + if err != nil { + t.Fatalf("Error on subscribe: %v", err) + } + + err = nc.Publish("foo", []byte("Hello World")) + if err != nil { + t.Fatalf("Error on publish: %v", err) + } + nc.Flush() + nc.Flush() + + if nr := atomic.LoadInt32(&r); nr != 0 { + t.Fatalf("Expected no messages echoed back, received %d\n", nr) + } +} + +// Trust Server Tests + +var ( + oSeed = []byte("SOAL7GTNI66CTVVNXBNQMG6V2HTDRWC3HGEP7D2OUTWNWSNYZDXWFOX4SU") + aSeed = []byte("SAAASUPRY3ONU4GJR7J5RUVYRUFZXG56F4WEXELLLORQ65AEPSMIFTOJGE") + uSeed = []byte("SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY") + + aJWT = "eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJLWjZIUVRXRlY3WkRZSFo3NklRNUhPM0pINDVRNUdJS0JNMzJTSENQVUJNNk5PNkU3TUhRIiwiaWF0IjoxNTQ0MDcxODg5LCJpc3MiOiJPRDJXMkk0TVZSQTVUR1pMWjJBRzZaSEdWTDNPVEtGV1FKRklYNFROQkVSMjNFNlA0NlMzNDVZWSIsInN1YiI6IkFBUFFKUVVQS1ZYR1c1Q1pINUcySEZKVUxZU0tERUxBWlJWV0pBMjZWRFpPN1dTQlVOSVlSRk5RIiwidHlwZSI6ImFjY291bnQiLCJuYXRzIjp7ImxpbWl0cyI6eyJzdWJzIjotMSwiY29ubiI6LTEsImltcG9ydHMiOi0xLCJleHBvcnRzIjotMSwiZGF0YSI6LTEsInBheWxvYWQiOi0xLCJ3aWxkY2FyZHMiOnRydWV9fX0.8o35JPQgvhgFT84Bi2Z-zAeSiLrzzEZn34sgr1DIBEDTwa-EEiMhvTeos9cvXxoZVCCadqZxAWVwS6paAMj8Bg" + + uJWT = "eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJBSFQzRzNXRElDS1FWQ1FUWFJUTldPRlVVUFRWNE00RFZQV0JGSFpJQUROWEZIWEpQR0FBIiwiaWF0IjoxNTQ0MDcxODg5LCJpc3MiOiJBQVBRSlFVUEtWWEdXNUNaSDVHMkhGSlVMWVNLREVMQVpSVldKQTI2VkRaTzdXU0JVTklZUkZOUSIsInN1YiI6IlVBVDZCV0NTQ1dMVUtKVDZLNk1CSkpPRU9UWFo1QUpET1lLTkVWUkZDN1ZOTzZPQTQzTjRUUk5PIiwidHlwZSI6InVzZXIiLCJuYXRzIjp7InB1YiI6e30sInN1YiI6e319fQ._8A1XM88Q2kp7XVJZ42bQuO9E3QPsNAGKtVjAkDycj8A5PtRPby9UpqBUZzBwiJQQO3TUcD5GGqSvsMm6X8hCQ" + + chained = ` +-----BEGIN NATS USER JWT----- +eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJBSFQzRzNXRElDS1FWQ1FUWFJUTldPRlVVUFRWNE00RFZQV0JGSFpJQUROWEZIWEpQR0FBIiwiaWF0IjoxNTQ0MDcxODg5LCJpc3MiOiJBQVBRSlFVUEtWWEdXNUNaSDVHMkhGSlVMWVNLREVMQVpSVldKQTI2VkRaTzdXU0JVTklZUkZOUSIsInN1YiI6IlVBVDZCV0NTQ1dMVUtKVDZLNk1CSkpPRU9UWFo1QUpET1lLTkVWUkZDN1ZOTzZPQTQzTjRUUk5PIiwidHlwZSI6InVzZXIiLCJuYXRzIjp7InB1YiI6e30sInN1YiI6e319fQ._8A1XM88Q2kp7XVJZ42bQuO9E3QPsNAGKtVjAkDycj8A5PtRPby9UpqBUZzBwiJQQO3TUcD5GGqSvsMm6X8hCQ +------END NATS USER JWT------ + +************************* IMPORTANT ************************* +NKEY Seed printed below can be used to sign and prove identity. +NKEYs are sensitive and should be treated as secrets. + +-----BEGIN USER NKEY SEED----- +SUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY +------END USER NKEY SEED------ +` +) + +func runTrustServer() *server.Server { + kp, _ := nkeys.FromSeed(oSeed) + pub, _ := kp.PublicKey() + opts := natsserver.DefaultTestOptions + opts.Port = TEST_PORT + opts.TrustedKeys = []string{string(pub)} + s := RunServerWithOptions(&opts) + mr := &server.MemAccResolver{} + akp, _ := nkeys.FromSeed(aSeed) + apub, _ := akp.PublicKey() + mr.Store(string(apub), aJWT) + s.SetAccountResolver(mr) + return s +} + +func createTmpFile(t *testing.T, content []byte) string { + t.Helper() + conf, err := os.CreateTemp("", "") + if err != nil { + t.Fatalf("Error creating conf file: %v", err) + } + fName := conf.Name() + conf.Close() + if err := os.WriteFile(fName, content, 0666); err != nil { + os.Remove(fName) + t.Fatalf("Error writing conf file: %v", err) + } + return fName +} + +func TestBasicUserJWTAuth(t *testing.T) { + if server.VERSION[0] == '1' { + t.Skip() + } + ts := runTrustServer() + defer ts.Shutdown() + + url := fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) + _, err := nats.Connect(url) + if err == nil { + t.Fatalf("Expecting an error on connect") + } + + jwtCB := func() (string, error) { + return uJWT, nil + } + sigCB := func(nonce []byte) ([]byte, error) { + kp, _ := nkeys.FromSeed(uSeed) + sig, _ := kp.Sign(nonce) + return sig, nil + } + + // Try with user jwt but no sig + _, err = nats.Connect(url, nats.UserJWT(jwtCB, nil)) + if err == nil { + t.Fatalf("Expecting an error on connect") + } + + // Try with user callback + _, err = nats.Connect(url, nats.UserJWT(nil, sigCB)) + if err == nil { + t.Fatalf("Expecting an error on connect") + } + + nc, err := nats.Connect(url, nats.UserJWT(jwtCB, sigCB)) + if err != nil { + t.Fatalf("Expected to connect, got %v", err) + } + nc.Close() +} + +func TestUserCredentialsTwoFiles(t *testing.T) { + if server.VERSION[0] == '1' { + t.Skip() + } + ts := runTrustServer() + defer ts.Shutdown() + + userJWTFile := createTmpFile(t, []byte(uJWT)) + defer os.Remove(userJWTFile) + userSeedFile := createTmpFile(t, uSeed) + defer os.Remove(userSeedFile) + + url := fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) + nc, err := nats.Connect(url, nats.UserCredentials(userJWTFile, userSeedFile)) + if err != nil { + t.Fatalf("Expected to connect, got %v", err) + } + nc.Close() +} + +func TestUserCredentialsChainedFile(t *testing.T) { + if server.VERSION[0] == '1' { + t.Skip() + } + ts := runTrustServer() + defer ts.Shutdown() + + chainedFile := createTmpFile(t, []byte(chained)) + defer os.Remove(chainedFile) + + url := fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) + nc, err := nats.Connect(url, nats.UserCredentials(chainedFile)) + if err != nil { + t.Fatalf("Expected to connect, got %v", err) + } + nc.Close() + + chainedFile = createTmpFile(t, []byte("invalid content")) + defer os.Remove(chainedFile) + nc, err = nats.Connect(url, nats.UserCredentials(chainedFile)) + if err == nil || !strings.Contains(err.Error(), + "error signing nonce: unable to extract key pair from file") { + if nc != nil { + nc.Close() + } + t.Fatalf("Expected error about invalid creds file, got %q", err) + } +} + +func TestReconnectMissingCredentials(t *testing.T) { + ts := runTrustServer() + defer ts.Shutdown() + + chainedFile := createTmpFile(t, []byte(chained)) + defer os.Remove(chainedFile) + + url := fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) + errs := make(chan error, 1) + nc, err := nats.Connect(url, nats.UserCredentials(chainedFile), nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) { + errs <- err + })) + if err != nil { + t.Fatalf("Expected to connect, got %v", err) + } + defer nc.Close() + os.Remove(chainedFile) + ts.Shutdown() + + ts = runTrustServer() + defer ts.Shutdown() + + select { + case err := <-errs: + if !strings.Contains(err.Error(), "no such file or directory") { + t.Fatalf("Expected error about missing creds file, got %q", err) + } + case <-time.After(5 * time.Second): + t.Fatal("Did not get error about missing creds file") + } +} + +func TestUserJWTAndSeed(t *testing.T) { + if server.VERSION[0] == '1' { + t.Skip() + } + ts := runTrustServer() + defer ts.Shutdown() + + url := fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) + nc, err := nats.Connect(url, nats.UserJWTAndSeed(uJWT, string(uSeed))) + if err != nil { + t.Fatalf("Expected to connect, got %v", err) + } + nc.Close() +} + +// If we are using TLS and have multiple servers we try to match the IP +// from a discovered server with the expected hostname for certs without IP +// designations. In certain cases where there is a not authorized error and +// we were trying the second server with the IP only and getting an error +// that was hard to understand for the end user. This did require +// Opts.Secure = false, but the fix removed the check on Opts.Secure to decide +// if we need to save off the hostname that we connected to first. +func TestUserCredentialsChainedFileNotFoundError(t *testing.T) { + if server.VERSION[0] == '1' { + t.Skip() + } + // Setup opts for both servers. + kp, _ := nkeys.FromSeed(oSeed) + pub, _ := kp.PublicKey() + opts := natsserver.DefaultTestOptions + opts.Port = -1 + opts.Cluster.Port = -1 + opts.TrustedKeys = []string{string(pub)} + tc := &server.TLSConfigOpts{ + CertFile: "./configs/certs/server_noip.pem", + KeyFile: "./configs/certs/key_noip.pem", + } + var err error + if opts.TLSConfig, err = server.GenTLSConfig(tc); err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + // copy the opts for the second server. + opts2 := opts + + sa := RunServerWithOptions(&opts) + defer sa.Shutdown() + + routeAddr := fmt.Sprintf("nats-route://%s:%d", opts.Cluster.Host, opts.Cluster.Port) + rurl, _ := url.Parse(routeAddr) + opts2.Routes = []*url.URL{rurl} + + sb := RunServerWithOptions(&opts2) + defer sb.Shutdown() + + wait := time.Now().Add(2 * time.Second) + for time.Now().Before(wait) { + sanr := sa.NumRoutes() + sbnr := sb.NumRoutes() + if sanr == 1 && sbnr == 1 { + break + } + time.Sleep(50 * time.Millisecond) + } + + // Make sure we get the right error here. + nc, err := nats.Connect(fmt.Sprintf("nats://localhost:%d", opts.Port), + nats.RootCAs("./configs/certs/ca.pem"), + nats.UserCredentials("filenotfound.creds")) + + if err == nil { + nc.Close() + t.Fatalf("Expected an error on missing credentials file") + } + if !strings.Contains(err.Error(), "no such file or directory") && + !strings.Contains(err.Error(), "The system cannot find the file specified") { + t.Fatalf("Expected a missing file error, got %q", err) + } +} + +var natsReconnectOpts = nats.Options{ + Url: fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT), + AllowReconnect: true, + MaxReconnect: 10, + ReconnectWait: 100 * time.Millisecond, + Timeout: nats.DefaultTimeout, +} + +func TestNkeyAuth(t *testing.T) { + if server.VERSION[0] == '1' { + t.Skip() + } + + seed := []byte("SUAKYRHVIOREXV7EUZTBHUHL7NUMHPMAS7QMDU3GTIUWEI5LDNOXD43IZY") + kp, _ := nkeys.FromSeed(seed) + pub, _ := kp.PublicKey() + + sopts := natsserver.DefaultTestOptions + sopts.Port = TEST_PORT + sopts.Nkeys = []*server.NkeyUser{{Nkey: string(pub)}} + ts := RunServerWithOptions(&sopts) + defer ts.Shutdown() + + opts := natsReconnectOpts + if _, err := opts.Connect(); err == nil { + t.Fatalf("Expected to fail with no nkey auth defined") + } + opts.Nkey = string(pub) + if _, err := opts.Connect(); err != nats.ErrNkeyButNoSigCB { + t.Fatalf("Expected to fail with nkey defined but no signature callback, got %v", err) + } + badSign := func(nonce []byte) ([]byte, error) { + return []byte("VALID?"), nil + } + opts.SignatureCB = badSign + if _, err := opts.Connect(); err == nil { + t.Fatalf("Expected to fail with nkey and bad signature callback") + } + goodSign := func(nonce []byte) ([]byte, error) { + sig, err := kp.Sign(nonce) + if err != nil { + t.Fatalf("Failed signing nonce: %v", err) + } + return sig, nil + } + opts.SignatureCB = goodSign + nc, err := opts.Connect() + if err != nil { + t.Fatalf("Expected to succeed but got %v", err) + } + defer nc.Close() + + // Now disconnect by killing the server and restarting. + ts.Shutdown() + ts = RunServerWithOptions(&sopts) + defer ts.Shutdown() + + if err := nc.FlushTimeout(5 * time.Second); err != nil { + t.Fatalf("Error on Flush: %v", err) + } +} + +func TestLookupHostResultIsRandomized(t *testing.T) { + orgAddrs, err := net.LookupHost("localhost") + if err != nil { + t.Fatalf("Error looking up host: %v", err) + } + + // We actually want the IPv4 and IPv6 addresses, so lets make sure. + if !reflect.DeepEqual(orgAddrs, []string{"::1", "127.0.0.1"}) { + t.Skip("Was looking for IPv4 and IPv6 addresses for localhost to perform test") + } + + opts := natsserver.DefaultTestOptions + opts.Host = "127.0.0.1" + opts.Port = TEST_PORT + s1 := RunServerWithOptions(&opts) + defer s1.Shutdown() + + opts.Host = "::1" + s2 := RunServerWithOptions(&opts) + defer s2.Shutdown() + + for i := 0; i < 100; i++ { + nc, err := nats.Connect(fmt.Sprintf("localhost:%d", TEST_PORT)) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + defer nc.Close() + } + + if ncls := s1.NumClients(); ncls < 35 || ncls > 65 { + t.Fatalf("Does not seem balanced between multiple servers: s1:%d, s2:%d", s1.NumClients(), s2.NumClients()) + } +} + +func TestLookupHostResultIsNotRandomizedWithNoRandom(t *testing.T) { + orgAddrs, err := net.LookupHost("localhost") + if err != nil { + t.Fatalf("Error looking up host: %v", err) + } + + // We actually want the IPv4 and IPv6 addresses, so lets make sure. + if !reflect.DeepEqual(orgAddrs, []string{"::1", "127.0.0.1"}) { + t.Skip("Was looking for IPv4 and IPv6 addresses for localhost to perform test") + } + + opts := natsserver.DefaultTestOptions + opts.Host = orgAddrs[0] + opts.Port = TEST_PORT + s1 := RunServerWithOptions(&opts) + defer s1.Shutdown() + + opts.Host = orgAddrs[1] + s2 := RunServerWithOptions(&opts) + defer s2.Shutdown() + + for i := 0; i < 100; i++ { + nc, err := nats.Connect(fmt.Sprintf("localhost:%d", TEST_PORT), nats.DontRandomize()) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + defer nc.Close() + } + + if ncls := s1.NumClients(); ncls != 100 { + t.Fatalf("Expected all clients on first server, only got %d of 100", ncls) + } +} + +func TestConnectedAddr(t *testing.T) { + s := RunServerOnPort(TEST_PORT) + defer s.Shutdown() + + var nc *nats.Conn + if addr := nc.ConnectedAddr(); addr != "" { + t.Fatalf("Expected empty result for nil connection, got %q", addr) + } + nc, err := nats.Connect(fmt.Sprintf("localhost:%d", TEST_PORT)) + if err != nil { + t.Fatalf("Error connecting: %v", err) + } + expected := s.Addr().String() + if addr := nc.ConnectedAddr(); addr != expected { + t.Fatalf("Expected address %q, got %q", expected, addr) + } + nc.Close() + if addr := nc.ConnectedAddr(); addr != "" { + t.Fatalf("Expected empty result for closed connection, got %q", addr) + } +} + +func TestSubscribeSyncRace(t *testing.T) { + s := RunServerOnPort(TEST_PORT) + defer s.Shutdown() + + nc, err := nats.Connect(fmt.Sprintf("127.0.0.1:%d", TEST_PORT)) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + defer nc.Close() + + go func() { + time.Sleep(time.Millisecond) + nc.Close() + }() + + subj := "foo.sync.race" + for i := 0; i < 10000; i++ { + if _, err := nc.SubscribeSync(subj); err != nil { + break + } + if _, err := nc.QueueSubscribeSync(subj, "gc"); err != nil { + break + } + } +} + +func TestBadSubjectsAndQueueNames(t *testing.T) { + s := RunServerOnPort(TEST_PORT) + defer s.Shutdown() + + nc, err := nats.Connect(fmt.Sprintf("127.0.0.1:%d", TEST_PORT)) + if err != nil { + t.Fatalf("Error connecting: %v", err) + } + defer nc.Close() + + // Make sure we get errors on bad subjects (spaces, etc) + // We want the client to protect the user. + badSubs := []string{"foo bar", "foo..bar", ".foo", "bar.baz.", "baz\t.foo"} + for _, subj := range badSubs { + if _, err := nc.SubscribeSync(subj); err != nats.ErrBadSubject { + t.Fatalf("Expected an error of ErrBadSubject for %q, got %v", subj, err) + } + } + + badQueues := []string{"foo group", "group\t1", "g1\r\n2"} + for _, q := range badQueues { + if _, err := nc.QueueSubscribeSync("foo", q); err != nats.ErrBadQueueName { + t.Fatalf("Expected an error of ErrBadQueueName for %q, got %v", q, err) + } + } +} + +func BenchmarkNextMsgNoTimeout(b *testing.B) { + s := RunServerOnPort(TEST_PORT) + defer s.Shutdown() + + ncp, err := nats.Connect(fmt.Sprintf("127.0.0.1:%d", TEST_PORT)) + if err != nil { + b.Fatalf("Error connecting: %v", err) + } + ncs, err := nats.Connect(fmt.Sprintf("127.0.0.1:%d", TEST_PORT), nats.SyncQueueLen(b.N)) + if err != nil { + b.Fatalf("Error connecting: %v", err) + } + + // Test processing speed so no long subject or payloads. + subj := "a" + + sub, err := ncs.SubscribeSync(subj) + if err != nil { + b.Fatalf("Error subscribing: %v", err) + } + ncs.Flush() + + // Set it up so we can internally queue all the messages. + sub.SetPendingLimits(b.N, b.N*1000) + + for i := 0; i < b.N; i++ { + ncp.Publish(subj, nil) + } + ncp.Flush() + + // Wait for them to all be queued up, testing NextMsg not server here. + // Only wait at most one second. + wait := time.Now().Add(time.Second) + for time.Now().Before(wait) { + nm, _, err := sub.Pending() + if err != nil { + b.Fatalf("Error on Pending() - %v", err) + } + if nm >= b.N { + break + } + time.Sleep(10 * time.Millisecond) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := sub.NextMsg(10 * time.Millisecond); err != nil { + b.Fatalf("Error getting message[%d]: %v", i, err) + } + } +} + +func TestAuthErrorOnReconnect(t *testing.T) { + // This is a bit of an artificial test, but it is to demonstrate + // that if the client is disconnected from a server (not due to an auth error), + // it will still correctly stop the reconnection logic if it gets twice an + // auth error from the same server. + + o1 := natsserver.DefaultTestOptions + o1.Port = -1 + s1 := RunServerWithOptions(&o1) + defer s1.Shutdown() + + o2 := natsserver.DefaultTestOptions + o2.Port = -1 + o2.Username = "ivan" + o2.Password = "pwd" + s2 := RunServerWithOptions(&o2) + defer s2.Shutdown() + + dch := make(chan bool) + cch := make(chan bool) + + urls := fmt.Sprintf("nats://%s:%d, nats://%s:%d", o1.Host, o1.Port, o2.Host, o2.Port) + nc, err := nats.Connect(urls, + nats.ReconnectWait(25*time.Millisecond), + nats.ReconnectJitter(0, 0), + nats.MaxReconnects(-1), + nats.DontRandomize(), + nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, _ error) {}), + nats.DisconnectErrHandler(func(_ *nats.Conn, e error) { + dch <- true + }), + nats.ClosedHandler(func(_ *nats.Conn) { + cch <- true + })) + if err != nil { + t.Fatalf("Expected to connect, got err: %v\n", err) + } + defer nc.Close() + + s1.Shutdown() + + // wait for disconnect + if e := WaitTime(dch, 5*time.Second); e != nil { + t.Fatal("Did not receive a disconnect callback message") + } + + // Wait for ClosedCB + if e := WaitTime(cch, 5*time.Second); e != nil { + reconnects := nc.Stats().Reconnects + t.Fatalf("Did not receive a closed callback message, #reconnects: %v", reconnects) + } + + // We should have stopped after 2 reconnects. + if reconnects := nc.Stats().Reconnects; reconnects != 2 { + t.Fatalf("Expected 2 reconnects, got %v", reconnects) + } + + // Expect connection to be closed... + if !nc.IsClosed() { + t.Fatalf("Wrong status: %d\n", nc.Status()) + } +} + +func TestStatsRace(t *testing.T) { + o := natsserver.DefaultTestOptions + o.Port = -1 + s := RunServerWithOptions(&o) + defer s.Shutdown() + + nc, err := nats.Connect(fmt.Sprintf("nats://%s:%d", o.Host, o.Port)) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + defer nc.Close() + + wg := sync.WaitGroup{} + wg.Add(1) + ch := make(chan bool) + go func() { + defer wg.Done() + for { + select { + case <-ch: + return + default: + nc.Stats() + } + } + }() + + nc.Subscribe("foo", func(_ *nats.Msg) {}) + for i := 0; i < 1000; i++ { + nc.Publish("foo", []byte("hello")) + } + + close(ch) + wg.Wait() +} + +func TestRequestLeaksMapEntries(t *testing.T) { + o := natsserver.DefaultTestOptions + o.Port = -1 + s := RunServerWithOptions(&o) + defer s.Shutdown() + + nc, err := nats.Connect(fmt.Sprintf("nats://%s:%d", o.Host, o.Port)) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + defer nc.Close() + + response := []byte("I will help you") + nc.Subscribe("foo", func(m *nats.Msg) { + nc.Publish(m.Reply, response) + }) + + for i := 0; i < 100; i++ { + msg, err := nc.Request("foo", nil, 500*time.Millisecond) + if err != nil { + t.Fatalf("Received an error on Request test: %s", err) + } + if !bytes.Equal(msg.Data, response) { + t.Fatalf("Received invalid response") + } + } +} + +func TestRequestMultipleReplies(t *testing.T) { + o := natsserver.DefaultTestOptions + o.Port = -1 + s := RunServerWithOptions(&o) + defer s.Shutdown() + + nc, err := nats.Connect(fmt.Sprintf("nats://%s:%d", o.Host, o.Port)) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + defer nc.Close() + + response := []byte("I will help you") + nc.Subscribe("foo", func(m *nats.Msg) { + m.Respond(response) + m.Respond(response) + }) + nc.Flush() + + nc2, err := nats.Connect(fmt.Sprintf("nats://%s:%d", o.Host, o.Port)) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + defer nc2.Close() + + errCh := make(chan error, 1) + // Send a request on bar and expect nothing + go func() { + if m, err := nc2.Request("bar", nil, 500*time.Millisecond); m != nil || err == nil { + errCh <- fmt.Errorf("Expected no reply, got m=%+v err=%v", m, err) + return + } + errCh <- nil + }() + + // Send a request on foo, we use only one of the 2 replies + if _, err := nc2.Request("foo", nil, time.Second); err != nil { + t.Fatalf("Received an error on Request test: %s", err) + } + if e := <-errCh; e != nil { + t.Fatal(e.Error()) + } +} + +func TestGetRTT(t *testing.T) { + s := RunServerOnPort(-1) + defer s.Shutdown() + + nc, err := nats.Connect(s.ClientURL(), nats.ReconnectWait(10*time.Millisecond), nats.ReconnectJitter(0, 0)) + if err != nil { + t.Fatalf("Expected to connect to server, got %v", err) + } + defer nc.Close() + + rtt, err := nc.RTT() + if err != nil { + t.Fatalf("Unexpected error getting RTT: %v", err) + } + if rtt > time.Second { + t.Fatalf("RTT value too large: %v", rtt) + } + // We should not get a value when in any disconnected state. + s.Shutdown() + time.Sleep(5 * time.Millisecond) + if _, err = nc.RTT(); err != nats.ErrDisconnected { + t.Fatalf("Expected disconnected error getting RTT when disconnected, got %v", err) + } +} + +func TestGetClientIP(t *testing.T) { + s := RunServerOnPort(-1) + defer s.Shutdown() + + nc, err := nats.Connect(s.ClientURL()) + if err != nil { + t.Fatalf("Expected to connect to server, got %v", err) + } + defer nc.Close() + + ip, err := nc.GetClientIP() + if err != nil { + t.Fatalf("Got error looking up IP: %v", err) + } + if !ip.IsLoopback() { + t.Fatalf("Expected a loopback IP, got %v", ip) + } + nc.Close() + if _, err := nc.GetClientIP(); err != nats.ErrConnectionClosed { + t.Fatalf("Expected a connection closed error, got %v", err) + } +} + +func TestReconnectWaitJitter(t *testing.T) { + s := RunServerOnPort(TEST_PORT) + defer s.Shutdown() + + rch := make(chan time.Time, 1) + nc, err := nats.Connect(s.ClientURL(), + nats.ReconnectWait(100*time.Millisecond), + nats.ReconnectJitter(500*time.Millisecond, 0), + nats.ReconnectHandler(func(_ *nats.Conn) { + rch <- time.Now() + }), + ) + if err != nil { + t.Fatalf("Error during connect: %v", err) + } + defer nc.Close() + + s.Shutdown() + start := time.Now() + // Wait a bit so that the library tries a first time without waiting. + time.Sleep(50 * time.Millisecond) + s = RunServerOnPort(TEST_PORT) + defer s.Shutdown() + select { + case end := <-rch: + dur := end.Sub(start) + // We should wait at least the reconnect wait + random up to 500ms. + // Account for a bit of variation since we rely on the reconnect + // handler which is not invoked in place. + if dur < 90*time.Millisecond || dur > 800*time.Millisecond { + t.Fatalf("Wrong wait: %v", dur) + } + case <-time.After(5 * time.Second): + t.Fatalf("Should have reconnected") + } + nc.Close() + + // Use a long reconnect wait + nc, err = nats.Connect(s.ClientURL(), nats.ReconnectWait(10*time.Minute)) + if err != nil { + t.Fatalf("Error during connect: %v", err) + } + defer nc.Close() + + // Cause a disconnect + s.Shutdown() + // Wait a bit for the reconnect loop to go into wait mode. + time.Sleep(50 * time.Millisecond) + s = RunServerOnPort(TEST_PORT) + defer s.Shutdown() + // Now close and expect the reconnect go routine to return.. + nc.Close() + // Wait a bit to give a chance for the go routine to exit. + time.Sleep(50 * time.Millisecond) + buf := make([]byte, 100000) + n := runtime.Stack(buf, true) + if strings.Contains(string(buf[:n]), "doReconnect") { + t.Fatalf("doReconnect go routine still running:\n%s", buf[:n]) + } +} + +func TestCustomReconnectDelay(t *testing.T) { + s := RunServerOnPort(TEST_PORT) + defer s.Shutdown() + + expectedAttempt := 1 + errCh := make(chan error, 1) + cCh := make(chan bool, 1) + nc, err := nats.Connect(s.ClientURL(), + nats.Timeout(100*time.Millisecond), // Need to lower for Windows tests + nats.CustomReconnectDelay(func(n int) time.Duration { + var err error + var delay time.Duration + if n != expectedAttempt { + err = fmt.Errorf("Expected attempt to be %v, got %v", expectedAttempt, n) + } else { + expectedAttempt++ + if n <= 4 { + delay = 100 * time.Millisecond + } + } + if err != nil { + select { + case errCh <- err: + default: + } + } + return delay + }), + nats.MaxReconnects(4), + nats.ClosedHandler(func(_ *nats.Conn) { + cCh <- true + }), + ) + if err != nil { + t.Fatalf("Error during connect: %v", err) + } + defer nc.Close() + + // Cause disconnect + s.Shutdown() + + // We should be trying to reconnect 4 times + start := time.Now() + + // Wait on error or completion of test. + select { + case e := <-errCh: + if e != nil { + t.Fatal(e.Error()) + } + case <-cCh: + case <-time.After(2 * time.Second): + t.Fatalf("No CB invoked") + } + // On Windows, a failed connect attempt will last as much as Timeout(), + // so we need to take that into account. + max := 500 * time.Millisecond + if runtime.GOOS == "windows" { + max = time.Second + } + if dur := time.Since(start); dur >= max { + t.Fatalf("Waited too long on each reconnect: %v", dur) + } +} + +func TestMsg_RespondMsg(t *testing.T) { + s := RunServerOnPort(-1) + defer s.Shutdown() + + nc, err := nats.Connect(s.ClientURL()) + if err != nil { + t.Fatalf("Expected to connect to server, got %v", err) + } + defer nc.Close() + + sub, err := nc.SubscribeSync(nats.NewInbox()) + if err != nil { + t.Fatalf("subscribe failed: %s", err) + } + + nc.PublishMsg(&nats.Msg{Reply: sub.Subject, Subject: sub.Subject, Data: []byte("request")}) + req, err := sub.NextMsg(time.Second) + if err != nil { + t.Fatalf("NextMsg failed: %s", err) + } + + // verifies that RespondMsg sets the reply subject on msg based on req + err = req.RespondMsg(&nats.Msg{Data: []byte("response")}) + if err != nil { + t.Fatalf("RespondMsg failed: %s", err) + } + + resp, err := sub.NextMsg(time.Second) + if err != nil { + t.Fatalf("NextMsg failed: %s", err) + } + + if !bytes.Equal(resp.Data, []byte("response")) { + t.Fatalf("did not get correct response: %q", resp.Data) + } +} + +func TestCustomInboxPrefix(t *testing.T) { + opts := &nats.Options{} + for _, p := range []string{"$BOB.", "$BOB.*", "$BOB.>", ">", ".", "", "BOB.*.X", "BOB.>.X"} { + err := nats.CustomInboxPrefix(p)(opts) + if err == nil { + t.Fatalf("Expected error for %q", p) + } + } + + s := RunServerOnPort(-1) + defer s.Shutdown() + + nc, err := nats.Connect(s.ClientURL(), nats.CustomInboxPrefix("$BOB")) + if err != nil { + t.Fatalf("Expected to connect to server, got %v", err) + } + defer nc.Close() + + sub, err := nc.Subscribe(nats.NewInbox(), func(msg *nats.Msg) { + if !strings.HasPrefix(msg.Reply, "$BOB.") { + t.Fatalf("invalid inbox subject %q received", msg.Reply) + } + + if len(strings.Split(msg.Reply, ".")) != 3 { + t.Fatalf("invalid number tokens in %s", msg.Reply) + } + + msg.Respond([]byte("ok")) + }) + if err != nil { + t.Fatalf("subscribe failed: %s", err) + } + + resp, err := nc.Request(sub.Subject, nil, time.Second) + if err != nil { + t.Fatalf("request failed: %s", err) + } + + if !bytes.Equal(resp.Data, []byte("ok")) { + t.Fatalf("did not receive ok: %q", resp.Data) + } +} + +func TestRespInbox(t *testing.T) { + s := RunServerOnPort(-1) + defer s.Shutdown() + + nc, err := nats.Connect(s.ClientURL()) + if err != nil { + t.Fatalf("Expected to connect to server, got %v", err) + } + defer nc.Close() + + if _, err := nc.Subscribe("foo", func(msg *nats.Msg) { + lastDot := strings.LastIndex(msg.Reply, ".") + if lastDot == -1 { + msg.Respond([]byte(fmt.Sprintf("Invalid reply subject: %q", msg.Reply))) + return + } + lastToken := msg.Reply[lastDot+1:] + replySuffixLen := 8 + if len(lastToken) != replySuffixLen { + msg.Respond([]byte(fmt.Sprintf("Invalid last token: %q", lastToken))) + return + } + msg.Respond(nil) + }); err != nil { + t.Fatalf("subscribe failed: %s", err) + } + resp, err := nc.Request("foo", []byte("check inbox"), time.Second) + if err != nil { + t.Fatalf("Request failed: %v", err) + } + if len(resp.Data) > 0 { + t.Fatalf("Error: %s", resp.Data) + } +} + +func TestInProcessConn(t *testing.T) { + s := RunServerOnPort(-1) + defer s.Shutdown() + + nc, err := nats.Connect("", nats.InProcessServer(s)) + if err != nil { + t.Fatal(err) + } + + defer nc.Close() + + // Status should be connected. + if nc.Status() != nats.CONNECTED { + t.Fatal("should be status CONNECTED") + } + + // The server should respond to a request. + if _, err := nc.RTT(); err != nil { + t.Fatal(err) + } +} diff --git a/test/norace_test.go b/test/norace_test.go index 5378c0868..cbbfc852f 100644 --- a/test/norace_test.go +++ b/test/norace_test.go @@ -1,4 +1,4 @@ -// Copyright 2019-2022 The NATS Authors +// Copyright 2019-2023 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 @@ -20,7 +20,10 @@ import ( "bytes" "context" "crypto/rand" + "fmt" "io" + "os" + "strings" "testing" "time" @@ -100,3 +103,667 @@ func TestNoRaceObjectDoublePut(t *testing.T) { _, err = obs.GetBytes("A") expectOk(t, err) } + +func TestNoRaceJetStreamConsumerSlowConsumer(t *testing.T) { + // This test fails many times, need to look harder at the imbalance. + t.SkipNow() + + s := RunServerOnPort(-1) + defer shutdownJSServerAndRemoveStorage(t, s) + + if err := s.EnableJetStream(nil); err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + nc, js := jsClient(t, s) + defer nc.Close() + + var err error + + _, err = js.AddStream(&nats.StreamConfig{ + Name: "PENDING_TEST", + Subjects: []string{"js.p"}, + Storage: nats.MemoryStorage, + }) + if err != nil { + t.Fatalf("stream create failed: %v", err) + } + + // Override default handler for test. + nc.SetErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, _ error) {}) + + // Queue up 1M small messages. + toSend := uint64(1000000) + for i := uint64(0); i < toSend; i++ { + nc.Publish("js.p", []byte("ok")) + } + nc.Flush() + + str, err := js.StreamInfo("PENDING_TEST") + if err != nil { + t.Fatal(err) + } + + if nm := str.State.Msgs; nm != toSend { + t.Fatalf("Expected to have stored all %d msgs, got only %d", toSend, nm) + } + + var received uint64 + done := make(chan bool, 1) + + js.Subscribe("js.p", func(m *nats.Msg) { + received++ + if received >= toSend { + done <- true + } + meta, err := m.Metadata() + if err != nil { + t.Fatalf("could not get message metadata: %s", err) + } + if meta.Sequence.Stream != received { + t.Errorf("Missed a sequence, was expecting %d but got %d, last error: '%v'", received, meta.Sequence.Stream, nc.LastError()) + nc.Close() + } + m.Ack() + }) + + select { + case <-time.After(5 * time.Second): + t.Fatalf("Failed to get all %d messages, only got %d", toSend, received) + case <-done: + } +} + +func TestNoRaceJetStreamPushFlowControlHeartbeats_SubscribeSync(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + errHandler := nats.ErrorHandler(func(c *nats.Conn, sub *nats.Subscription, err error) { + t.Logf("WARN: %s", err) + }) + + nc, js := jsClient(t, s, errHandler) + defer nc.Close() + + var err error + + _, err = js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"foo"}, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Burst and try to hit the flow control limit of the server. + const totalMsgs = 16536 + payload := strings.Repeat("A", 1024) + for i := 0; i < totalMsgs; i++ { + if _, err := js.Publish("foo", []byte(fmt.Sprintf("i:%d/", i)+payload)); err != nil { + t.Fatal(err) + } + } + + hbTimer := 100 * time.Millisecond + sub, err := js.SubscribeSync("foo", + nats.AckWait(30*time.Second), + nats.MaxDeliver(1), + nats.EnableFlowControl(), + nats.IdleHeartbeat(hbTimer), + ) + if err != nil { + t.Fatal(err) + } + defer sub.Unsubscribe() + + info, err := sub.ConsumerInfo() + if err != nil { + t.Fatal(err) + } + if !info.Config.FlowControl { + t.Fatal("Expected Flow Control to be enabled") + } + if info.Config.Heartbeat != hbTimer { + t.Errorf("Expected %v, got: %v", hbTimer, info.Config.Heartbeat) + } + + m, err := sub.NextMsg(1 * time.Second) + if err != nil { + t.Fatalf("Error getting next message: %v", err) + } + meta, err := m.Metadata() + if err != nil { + t.Fatal(err) + } + if meta.NumPending > totalMsgs { + t.Logf("WARN: More pending messages than expected (%v), got: %v", totalMsgs, meta.NumPending) + } + err = m.Ack() + if err != nil { + t.Fatal(err) + } + + recvd := 1 + timeout := time.Now().Add(10 * time.Second) + for time.Now().Before(timeout) { + m, err := sub.NextMsg(1 * time.Second) + if err != nil { + t.Fatalf("Error getting next message: %v", err) + } + if len(m.Data) == 0 { + t.Fatalf("Unexpected empty message: %+v", m) + } + + if err := m.AckSync(); err != nil { + t.Fatalf("Error on ack message: %v", err) + } + recvd++ + + if recvd == totalMsgs { + break + } + } + + t.Run("idle heartbeats", func(t *testing.T) { + // Delay to get a few heartbeats. + time.Sleep(4 * hbTimer) + + timeout = time.Now().Add(5 * time.Second) + for time.Now().Before(timeout) { + msg, err := sub.NextMsg(200 * time.Millisecond) + if err != nil { + if err == nats.ErrTimeout { + // If timeout, ok to stop checking for the test. + break + } + t.Fatal(err) + } + if len(msg.Data) == 0 { + t.Fatalf("Unexpected empty message: %+v", m) + } + + recvd++ + meta, err := msg.Metadata() + if err != nil { + t.Fatal(err) + } + if meta.NumPending == 0 { + break + } + } + if recvd > totalMsgs { + t.Logf("WARN: Received more messages than expected (%v), got: %v", totalMsgs, recvd) + } + }) + + t.Run("with context", func(t *testing.T) { + sub, err := js.SubscribeSync("foo", + nats.AckWait(30*time.Second), + nats.Durable("bar"), + nats.EnableFlowControl(), + nats.IdleHeartbeat(hbTimer), + ) + if err != nil { + t.Fatal(err) + } + defer sub.Unsubscribe() + + info, err = sub.ConsumerInfo() + if err != nil { + t.Fatal(err) + } + if !info.Config.FlowControl { + t.Fatal("Expected Flow Control to be enabled") + } + + recvd = 0 + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + for { + select { + case <-ctx.Done(): + t.Fatal(ctx.Err()) + default: + } + + m, err := sub.NextMsgWithContext(ctx) + if err != nil { + t.Fatalf("Error getting next message: %v", err) + } + if len(m.Data) == 0 { + t.Fatalf("Unexpected empty message: %+v", m) + } + + if err := m.Ack(); err != nil { + t.Fatalf("Error on ack message: %v", err) + } + recvd++ + + if recvd >= totalMsgs { + break + } + } + + // Delay to get a few heartbeats. + time.Sleep(4 * hbTimer) + ctx, cancel = context.WithTimeout(context.Background(), time.Second) + defer cancel() + FOR_LOOP: + for { + select { + case <-ctx.Done(): + if ctx.Err() == context.DeadlineExceeded { + break FOR_LOOP + } + default: + } + + msg, err := sub.NextMsgWithContext(ctx) + if err != nil { + if err == context.DeadlineExceeded { + break + } + t.Fatal(err) + } + if len(msg.Data) == 0 { + t.Fatalf("Unexpected empty message: %+v", m) + } + recvd++ + meta, err := msg.Metadata() + if err != nil { + t.Fatal(err) + } + if meta.NumPending == 0 { + break + } + } + if recvd > totalMsgs { + t.Logf("WARN: Received more messages than expected (%v), got: %v", totalMsgs, recvd) + } + }) +} + +func TestNoRaceJetStreamPushFlowControlHeartbeats_SubscribeAsync(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + var err error + + _, err = js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"foo"}, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Burst and try to hit the flow control limit of the server. + const totalMsgs = 16536 + payload := strings.Repeat("A", 1024) + for i := 0; i < totalMsgs; i++ { + if _, err := js.Publish("foo", []byte(payload)); err != nil { + t.Fatal(err) + } + } + + recvd := make(chan *nats.Msg, totalMsgs) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + errCh := make(chan error) + hbTimer := 100 * time.Millisecond + sub, err := js.Subscribe("foo", func(msg *nats.Msg) { + if len(msg.Data) == 0 { + errCh <- fmt.Errorf("Unexpected empty message: %+v", msg) + } + recvd <- msg + + if len(recvd) == totalMsgs { + cancel() + } + }, nats.EnableFlowControl(), nats.IdleHeartbeat(hbTimer)) + if err != nil { + t.Fatal(err) + } + defer sub.Unsubscribe() + + info, err := sub.ConsumerInfo() + if err != nil { + t.Fatal(err) + } + if !info.Config.FlowControl { + t.Fatal("Expected Flow Control to be enabled") + } + if info.Config.Heartbeat != hbTimer { + t.Errorf("Expected %v, got: %v", hbTimer, info.Config.Heartbeat) + } + + <-ctx.Done() + + got := len(recvd) + expected := totalMsgs + if got != expected { + t.Errorf("Expected %v, got: %v", expected, got) + } + + // Wait for a couple of heartbeats to arrive and confirm there is no error. + select { + case <-time.After(1 * time.Second): + case err := <-errCh: + t.Fatal(err) + } +} + +func TestNoRaceJetStreamPushFlowControlHeartbeats_ChanSubscribe(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + errHandler := nats.ErrorHandler(func(c *nats.Conn, sub *nats.Subscription, err error) { + t.Logf("WARN: %s : %v", err, sub.Subject) + }) + + nc, js := jsClient(t, s, errHandler) + defer nc.Close() + + var err error + + _, err = js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"foo"}, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Burst and try to hit the flow control limit of the server. + const totalMsgs = 16536 + payload := strings.Repeat("A", 1024) + for i := 0; i < totalMsgs; i++ { + if _, err := js.Publish("foo", []byte(fmt.Sprintf("i:%d/", i)+payload)); err != nil { + t.Fatal(err) + } + } + + hbTimer := 100 * time.Millisecond + mch := make(chan *nats.Msg, 16536) + sub, err := js.ChanSubscribe("foo", mch, + nats.AckWait(30*time.Second), + nats.MaxDeliver(1), + nats.EnableFlowControl(), + nats.IdleHeartbeat(hbTimer), + ) + if err != nil { + t.Fatal(err) + } + defer sub.Unsubscribe() + + info, err := sub.ConsumerInfo() + if err != nil { + t.Fatal(err) + } + if !info.Config.FlowControl { + t.Fatal("Expected Flow Control to be enabled") + } + if info.Config.Heartbeat != hbTimer { + t.Errorf("Expected %v, got: %v", hbTimer, info.Config.Heartbeat) + } + + getNextMsg := func(mch chan *nats.Msg, timeout time.Duration) (*nats.Msg, error) { + t.Helper() + select { + case m := <-mch: + return m, nil + case <-time.After(timeout): + return nil, nats.ErrTimeout + } + } + + m, err := getNextMsg(mch, 1*time.Second) + if err != nil { + t.Fatalf("Error getting next message: %v", err) + } + meta, err := m.Metadata() + if err != nil { + t.Fatal(err) + } + if meta.NumPending > totalMsgs { + t.Logf("WARN: More pending messages than expected (%v), got: %v", totalMsgs, meta.NumPending) + } + err = m.Ack() + if err != nil { + t.Fatal(err) + } + + recvd := 1 + ctx, done := context.WithTimeout(context.Background(), 10*time.Second) + defer done() + +Loop: + for { + select { + case <-ctx.Done(): + break Loop + case m := <-mch: + if err != nil { + t.Fatalf("Error getting next message: %v", err) + } + if len(m.Data) == 0 { + t.Fatalf("Unexpected empty message: %+v", m) + } + + if err := m.Ack(); err != nil { + t.Fatalf("Error on ack message: %v", err) + } + recvd++ + + if recvd == totalMsgs { + done() + } + } + } + + t.Run("idle heartbeats", func(t *testing.T) { + // Delay to get a few heartbeats. + time.Sleep(4 * hbTimer) + + ctx, done := context.WithTimeout(context.Background(), 1*time.Second) + defer done() + Loop: + for { + select { + case <-ctx.Done(): + break Loop + case msg := <-mch: + if err != nil { + if err == nats.ErrTimeout { + // If timeout, ok to stop checking for the test. + break Loop + } + t.Fatal(err) + } + if len(msg.Data) == 0 { + t.Fatalf("Unexpected empty message: %+v", m) + } + + recvd++ + meta, err := msg.Metadata() + if err != nil { + t.Fatal(err) + } + if meta.NumPending == 0 { + break Loop + } + } + } + if recvd > totalMsgs { + t.Logf("WARN: Received more messages than expected (%v), got: %v", totalMsgs, recvd) + } + }) +} + +func TestNoRaceJetStreamPushFlowControl_SubscribeAsyncAndChannel(t *testing.T) { + s := RunBasicJetStreamServer() + defer shutdownJSServerAndRemoveStorage(t, s) + + errCh := make(chan error) + errHandler := nats.ErrorHandler(func(c *nats.Conn, sub *nats.Subscription, err error) { + errCh <- err + }) + nc, err := nats.Connect(s.ClientURL(), errHandler) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer nc.Close() + + const totalMsgs = 10_000 + + js, err := nc.JetStream(nats.PublishAsyncMaxPending(totalMsgs)) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + _, err = js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"foo"}, + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + go func() { + payload := strings.Repeat("O", 4096) + for i := 0; i < totalMsgs; i++ { + js.PublishAsync("foo", []byte(payload)) + } + }() + + // Small channel that blocks and then buffered channel that can deliver all + // messages without blocking. + recvd := make(chan *nats.Msg, 64) + delivered := make(chan *nats.Msg, totalMsgs) + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + + // Dispatch channel consumer + go func() { + for m := range recvd { + select { + case <-ctx.Done(): + return + default: + } + + delivered <- m + if len(delivered) == totalMsgs { + cancel() + return + } + } + }() + + sub, err := js.Subscribe("foo", func(msg *nats.Msg) { + // Cause bottleneck by having channel block when full + // because of work taking long. + recvd <- msg + }, nats.EnableFlowControl(), nats.IdleHeartbeat(5*time.Second)) + + if err != nil { + t.Fatal(err) + } + defer sub.Unsubscribe() + + // Set this lower then normal to make sure we do not exceed bytes pending with FC turned on. + sub.SetPendingLimits(totalMsgs, 4*1024*1024) // This matches server window for flowcontrol. + + info, err := sub.ConsumerInfo() + if err != nil { + t.Fatal(err) + } + if !info.Config.FlowControl { + t.Fatal("Expected Flow Control to be enabled") + } + <-ctx.Done() + + got := len(delivered) + expected := totalMsgs + if got != expected { + t.Errorf("Expected %d messages, got: %d", expected, got) + } + + // Wait for a couple of heartbeats to arrive and confirm there is no error. + select { + case <-time.After(1 * time.Second): + case err := <-errCh: + t.Errorf("error handler: %v", err) + } +} + +func TestNoRaceJetStreamChanSubscribeStall(t *testing.T) { + conf := createConfFile(t, []byte(` + listen: 127.0.0.1:-1 + jetstream: enabled + no_auth_user: pc + accounts: { + JS: { + jetstream: enabled + users: [ {user: pc, password: foo} ] + }, + } + `)) + defer os.Remove(conf) + + s, _ := RunServerWithConfig(conf) + defer shutdownJSServerAndRemoveStorage(t, s) + + nc, js := jsClient(t, s) + defer nc.Close() + + var err error + + // Create a stream. + if _, err = js.AddStream(&nats.StreamConfig{Name: "STALL"}); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + _, err = js.StreamInfo("STALL") + if err != nil { + t.Fatalf("stream lookup failed: %v", err) + } + + msg := []byte(strings.Repeat("A", 512)) + toSend := 100_000 + for i := 0; i < toSend; i++ { + // Use plain NATS here for speed. + nc.Publish("STALL", msg) + } + nc.Flush() + + batch := 100 + msgs := make(chan *nats.Msg, batch-2) + sub, err := js.ChanSubscribe("STALL", msgs, + nats.Durable("dlc"), + nats.EnableFlowControl(), + nats.IdleHeartbeat(5*time.Second), + nats.MaxAckPending(batch-2), + ) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer sub.Unsubscribe() + + for received := 0; received < toSend; { + select { + case m := <-msgs: + received++ + meta, _ := m.Metadata() + if meta.Sequence.Consumer != uint64(received) { + t.Fatalf("Missed something, wanted %d but got %d", received, meta.Sequence.Consumer) + } + m.Ack() + case <-time.After(time.Second): + t.Fatalf("Timeout waiting for messages, last received was %d", received) + } + } +} diff --git a/test/reconnect_test.go b/test/reconnect_test.go index d1023a21d..724a7e21b 100644 --- a/test/reconnect_test.go +++ b/test/reconnect_test.go @@ -27,7 +27,7 @@ import ( ) func startReconnectServer(t *testing.T) *server.Server { - return RunServerOnPort(22222) + return RunServerOnPort(TEST_PORT) } func TestReconnectTotalTime(t *testing.T) { @@ -55,7 +55,7 @@ func TestReconnectDisallowedFlags(t *testing.T) { ch := make(chan bool) opts := nats.GetDefaultOptions() - opts.Url = "nats://127.0.0.1:22222" + opts.Url = fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) opts.AllowReconnect = false opts.ClosedCB = func(_ *nats.Conn) { ch <- true @@ -79,7 +79,7 @@ func TestReconnectAllowedFlags(t *testing.T) { ch := make(chan bool) dch := make(chan bool) opts := nats.GetDefaultOptions() - opts.Url = "nats://127.0.0.1:22222" + opts.Url = fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) opts.AllowReconnect = true opts.MaxReconnect = 2 opts.ReconnectWait = 1 * time.Second @@ -119,14 +119,6 @@ func TestReconnectAllowedFlags(t *testing.T) { nc.Opts.ClosedCB = nil } -var reconnectOpts = nats.Options{ - Url: "nats://127.0.0.1:22222", - AllowReconnect: true, - MaxReconnect: 10, - ReconnectWait: 100 * time.Millisecond, - Timeout: nats.DefaultTimeout, -} - func TestConnCloseBreaksReconnectLoop(t *testing.T) { ts := startReconnectServer(t) defer ts.Shutdown() @@ -414,7 +406,7 @@ func TestIsClosed(t *testing.T) { ts := startReconnectServer(t) defer ts.Shutdown() - nc := NewConnection(t, 22222) + nc := NewConnection(t, TEST_PORT) defer nc.Close() if nc.IsClosed() { @@ -442,7 +434,7 @@ func TestIsReconnectingAndStatus(t *testing.T) { disconnectedch := make(chan bool, 3) reconnectch := make(chan bool, 2) opts := nats.GetDefaultOptions() - opts.Url = "nats://127.0.0.1:22222" + opts.Url = fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) opts.AllowReconnect = true opts.MaxReconnect = 10000 opts.ReconnectWait = 100 * time.Millisecond @@ -512,7 +504,7 @@ func TestFullFlushChanDuringReconnect(t *testing.T) { reconnectch := make(chan bool, 2) opts := nats.GetDefaultOptions() - opts.Url = "nats://127.0.0.1:22222" + opts.Url = fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT) opts.AllowReconnect = true opts.MaxReconnect = 10000 opts.ReconnectWait = 100 * time.Millisecond @@ -738,6 +730,14 @@ func TestReconnectTLSHostNoIP(t *testing.T) { } } +var reconnectOpts = nats.Options{ + Url: fmt.Sprintf("nats://127.0.0.1:%d", TEST_PORT), + AllowReconnect: true, + MaxReconnect: 10, + ReconnectWait: 100 * time.Millisecond, + Timeout: nats.DefaultTimeout, +} + func TestConnCloseNoCallback(t *testing.T) { ts := startReconnectServer(t) defer ts.Shutdown() diff --git a/test/ws_test.go b/test/ws_test.go new file mode 100644 index 000000000..15707d8b3 --- /dev/null +++ b/test/ws_test.go @@ -0,0 +1,612 @@ +// Copyright 2023 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 ( + "bytes" + "crypto/tls" + "encoding/binary" + "fmt" + "math/rand" + "net" + "runtime" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/nats-io/nats-server/v2/server" + natsserver "github.com/nats-io/nats-server/v2/test" + "github.com/nats-io/nats.go" + "github.com/nats-io/nuid" +) + +func testWSGetDefaultOptions(t *testing.T, tls bool) *server.Options { + t.Helper() + sopts := natsserver.DefaultTestOptions + sopts.Host = "127.0.0.1" + sopts.Port = -1 + sopts.Websocket.Host = "127.0.0.1" + sopts.Websocket.Port = -1 + sopts.Websocket.NoTLS = !tls + if tls { + tc := &server.TLSConfigOpts{ + CertFile: "./configs/certs/server.pem", + KeyFile: "./configs/certs/key.pem", + CaFile: "./configs/certs/ca.pem", + } + tlsConfig, err := server.GenTLSConfig(tc) + if err != nil { + t.Fatalf("Can't build TLCConfig: %v", err) + } + sopts.Websocket.TLSConfig = tlsConfig + } + return &sopts +} + +func TestWSBasic(t *testing.T) { + sopts := testWSGetDefaultOptions(t, false) + s := RunServerWithOptions(sopts) + defer s.Shutdown() + + url := fmt.Sprintf("ws://127.0.0.1:%d", sopts.Websocket.Port) + nc, err := nats.Connect(url) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + defer nc.Close() + + sub, err := nc.SubscribeSync("foo") + if err != nil { + t.Fatalf("Error on subscribe: %v", err) + } + + msgs := make([][]byte, 100) + for i := 0; i < len(msgs); i++ { + msg := make([]byte, rand.Intn(70000)) + for j := 0; j < len(msg); j++ { + msg[j] = 'A' + byte(rand.Intn(26)) + } + msgs[i] = msg + } + for i, msg := range msgs { + if err := nc.Publish("foo", msg); err != nil { + t.Fatalf("Error on publish: %v", err) + } + // Make sure that masking does not overwrite user data + if !bytes.Equal(msgs[i], msg) { + t.Fatalf("User content has been changed: %v, got %v", msgs[i], msg) + } + } + + for i := 0; i < len(msgs); i++ { + msg, err := sub.NextMsg(time.Second) + if err != nil { + t.Fatalf("Error getting next message: %v", err) + } + if !bytes.Equal(msgs[i], msg.Data) { + t.Fatalf("Expected message: %v, got %v", msgs[i], msg) + } + } +} + +func TestWSControlFrames(t *testing.T) { + sopts := testWSGetDefaultOptions(t, false) + s := RunServerWithOptions(sopts) + defer s.Shutdown() + + rch := make(chan bool, 10) + ncSub, err := nats.Connect(s.ClientURL(), + nats.ReconnectWait(50*time.Millisecond), + nats.ReconnectHandler(func(_ *nats.Conn) { rch <- true }), + ) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + defer ncSub.Close() + + sub, err := ncSub.SubscribeSync("foo") + if err != nil { + t.Fatalf("Error on subscribe: %v", err) + } + if err := ncSub.Flush(); err != nil { + t.Fatalf("Error on flush: %v", err) + } + + dch := make(chan error, 10) + url := fmt.Sprintf("ws://127.0.0.1:%d", sopts.Websocket.Port) + nc, err := nats.Connect(url, + nats.ReconnectWait(50*time.Millisecond), + nats.DisconnectErrHandler(func(_ *nats.Conn, err error) { dch <- err }), + nats.ReconnectHandler(func(_ *nats.Conn) { rch <- true }), + ) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + defer nc.Close() + + // Shutdown the server, which should send a close message, which by + // spec the client will try to echo back. + s.Shutdown() + + select { + case <-dch: + // OK + case <-time.After(time.Second): + t.Fatal("Should have been disconnected") + } + + s = RunServerWithOptions(sopts) + defer s.Shutdown() + + // Wait for both connections to reconnect + if err := Wait(rch); err != nil { + t.Fatalf("Should have reconnected: %v", err) + } + if err := Wait(rch); err != nil { + t.Fatalf("Should have reconnected: %v", err) + } + // Even if the first connection reconnects, there is no guarantee + // that the resend of SUB has yet been processed by the server. + // Doing a flush here will give us the guarantee. + if err := ncSub.Flush(); err != nil { + t.Fatalf("Error on flush: %v", err) + } + + // Publish and close connection. + if err := nc.Publish("foo", []byte("msg")); err != nil { + t.Fatalf("Error on publish: %v", err) + } + if err := nc.Flush(); err != nil { + t.Fatalf("Error on flush: %v", err) + } + nc.Close() + + if _, err := sub.NextMsg(time.Second); err != nil { + t.Fatalf("Did not get message: %v", err) + } +} + +func TestWSConcurrentConns(t *testing.T) { + sopts := testWSGetDefaultOptions(t, false) + s := RunServerWithOptions(sopts) + defer s.Shutdown() + + url := fmt.Sprintf("ws://127.0.0.1:%d", sopts.Websocket.Port) + + total := 50 + errCh := make(chan error, total) + wg := sync.WaitGroup{} + wg.Add(total) + for i := 0; i < total; i++ { + go func() { + defer wg.Done() + + nc, err := nats.Connect(url) + if err != nil { + errCh <- fmt.Errorf("Error on connect: %v", err) + return + } + defer nc.Close() + + sub, err := nc.SubscribeSync(nuid.Next()) + if err != nil { + errCh <- fmt.Errorf("Error on subscribe: %v", err) + return + } + nc.Publish(sub.Subject, []byte("here")) + if _, err := sub.NextMsg(time.Second); err != nil { + errCh <- err + } + }() + } + wg.Wait() + select { + case e := <-errCh: + t.Fatal(e.Error()) + default: + } +} + +func TestWSCompression(t *testing.T) { + msgSize := rand.Intn(40000) + for _, test := range []struct { + name string + srvCompression bool + cliCompression bool + }{ + {"srv_off_cli_off", false, false}, + {"srv_off_cli_on", false, true}, + {"srv_on_cli_off", true, false}, + {"srv_on_cli_on", true, true}, + } { + t.Run(test.name, func(t *testing.T) { + sopts := testWSGetDefaultOptions(t, false) + sopts.Websocket.Compression = test.srvCompression + s := RunServerWithOptions(sopts) + defer s.Shutdown() + + url := fmt.Sprintf("ws://127.0.0.1:%d", sopts.Websocket.Port) + var opts []nats.Option + if test.cliCompression { + opts = append(opts, nats.Compression(true)) + } + nc, err := nats.Connect(url, opts...) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + defer nc.Close() + + sub, err := nc.SubscribeSync("foo") + if err != nil { + t.Fatalf("Error on subscribe: %v", err) + } + + msgs := make([][]byte, 100) + for i := 0; i < len(msgs); i++ { + msg := make([]byte, msgSize) + for j := 0; j < len(msg); j++ { + msg[j] = 'A' + } + msgs[i] = msg + } + for i, msg := range msgs { + if err := nc.Publish("foo", msg); err != nil { + t.Fatalf("Error on publish: %v", err) + } + // Make sure that compression/masking does not touch user data + if !bytes.Equal(msgs[i], msg) { + t.Fatalf("User content has been changed: %v, got %v", msgs[i], msg) + } + } + + for i := 0; i < len(msgs); i++ { + msg, err := sub.NextMsg(time.Second) + if err != nil { + t.Fatalf("Error getting next message (%d): %v", i+1, err) + } + if !bytes.Equal(msgs[i], msg.Data) { + t.Fatalf("Expected message (%d): %v, got %v", i+1, msgs[i], msg) + } + } + }) + } +} + +func TestWSWithTLS(t *testing.T) { + for _, test := range []struct { + name string + compression bool + }{ + {"without compression", false}, + {"with compression", true}, + } { + t.Run(test.name, func(t *testing.T) { + sopts := testWSGetDefaultOptions(t, true) + sopts.Websocket.Compression = test.compression + s := RunServerWithOptions(sopts) + defer s.Shutdown() + + var copts []nats.Option + if test.compression { + copts = append(copts, nats.Compression(true)) + } + + // Check that we fail to connect without proper TLS configuration. + nc, err := nats.Connect(fmt.Sprintf("ws://localhost:%d", sopts.Websocket.Port), copts...) + if err == nil { + if nc != nil { + nc.Close() + } + t.Fatal("Expected error, got none") + } + + // Same but with wss protocol, which should translate to TLS, however, + // since we used self signed certificates, this should fail without + // asking to skip server cert verification. + nc, err = nats.Connect(fmt.Sprintf("wss://localhost:%d", sopts.Websocket.Port), copts...) + // Since Go 1.18, we had to regenerate certs to not have to use GODEBUG="x509sha1=1" + // But on macOS, with our test CA certs, no SCTs included, it will fail + // for the reason "x509: “localhost” certificate is not standards compliant" + // instead of "unknown authority". + if err == nil || (!strings.Contains(err.Error(), "authority") && !strings.Contains(err.Error(), "compliant")) { + if nc != nil { + nc.Close() + } + t.Fatalf("Expected error about unknown authority: %v", err) + } + + // Skip server verification and we should be good. + copts = append(copts, nats.Secure(&tls.Config{InsecureSkipVerify: true})) + nc, err = nats.Connect(fmt.Sprintf("wss://localhost:%d", sopts.Websocket.Port), copts...) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + defer nc.Close() + + sub, err := nc.SubscribeSync("foo") + if err != nil { + t.Fatalf("Error on subscribe: %v", err) + } + if err := nc.Publish("foo", []byte("hello")); err != nil { + t.Fatalf("Error on publish: %v", err) + } + if msg, err := sub.NextMsg(time.Second); err != nil { + t.Fatalf("Did not get message: %v", err) + } else if got := string(msg.Data); got != "hello" { + t.Fatalf("Expected %q, got %q", "hello", got) + } + }) + } +} + +type testSkipTLSDialer struct { + dialer *net.Dialer + skipTLS bool +} + +func (sd *testSkipTLSDialer) Dial(network, address string) (net.Conn, error) { + return sd.dialer.Dial(network, address) +} + +func (sd *testSkipTLSDialer) SkipTLSHandshake() bool { + return sd.skipTLS +} + +func TestWSWithTLSCustomDialer(t *testing.T) { + sopts := testWSGetDefaultOptions(t, true) + s := RunServerWithOptions(sopts) + defer s.Shutdown() + + sd := &testSkipTLSDialer{ + dialer: &net.Dialer{ + Timeout: 2 * time.Second, + }, + skipTLS: true, + } + + // Connect with CustomDialer that fails since TLSHandshake is disabled. + copts := make([]nats.Option, 0) + copts = append(copts, nats.Secure(&tls.Config{InsecureSkipVerify: true})) + copts = append(copts, nats.SetCustomDialer(sd)) + _, err := nats.Connect(fmt.Sprintf("wss://localhost:%d", sopts.Websocket.Port), copts...) + if err == nil { + t.Fatalf("Expected error on connect: %v", err) + } + if err.Error() != `invalid websocket connection` { + t.Logf("Expected invalid websocket connection: %v", err) + } + + // Retry with the dialer. + copts = make([]nats.Option, 0) + sd = &testSkipTLSDialer{ + dialer: &net.Dialer{ + Timeout: 2 * time.Second, + }, + skipTLS: false, + } + copts = append(copts, nats.Secure(&tls.Config{InsecureSkipVerify: true})) + copts = append(copts, nats.SetCustomDialer(sd)) + nc, err := nats.Connect(fmt.Sprintf("wss://localhost:%d", sopts.Websocket.Port), copts...) + if err != nil { + t.Fatalf("Unexpected error on connect: %v", err) + } + defer nc.Close() +} + +func TestWSGossipAndReconnect(t *testing.T) { + o1 := testWSGetDefaultOptions(t, false) + o1.ServerName = "A" + o1.Cluster.Host = "127.0.0.1" + o1.Cluster.Name = "abc" + o1.Cluster.Port = -1 + s1 := RunServerWithOptions(o1) + defer s1.Shutdown() + + o2 := testWSGetDefaultOptions(t, false) + o2.ServerName = "B" + o2.Cluster.Host = "127.0.0.1" + o2.Cluster.Name = "abc" + o2.Cluster.Port = -1 + o2.Routes = server.RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", o1.Cluster.Port)) + s2 := RunServerWithOptions(o2) + defer s2.Shutdown() + + rch := make(chan bool, 10) + url := fmt.Sprintf("ws://127.0.0.1:%d", o1.Websocket.Port) + nc, err := nats.Connect(url, + nats.ReconnectWait(50*time.Millisecond), + nats.ReconnectHandler(func(_ *nats.Conn) { rch <- true }), + ) + if err != nil { + t.Fatalf("Error on connect: %v", err) + } + defer nc.Close() + + timeout := time.Now().Add(time.Second) + for time.Now().Before(timeout) { + if len(nc.Servers()) > 1 { + break + } + time.Sleep(15 * time.Millisecond) + } + if len(nc.Servers()) == 1 { + t.Fatal("Did not discover server 2") + } + s1.Shutdown() + + // Wait for reconnect + if err := Wait(rch); err != nil { + t.Fatalf("Did not reconnect: %v", err) + } +} + +func TestWSStress(t *testing.T) { + // Enable this test only when wanting to stress test the system, say after + // some changes in the library or if a bug is found. Also, don't run it + // with the `-race` flag! + t.SkipNow() + // Total producers (there will be 2 per subject) + prods := 4 + // Total messages sent + total := int64(1000000) + // Total messages received, there is 2 consumer per subject + totalRecv := 2 * total + // We will create a "golden" slice from which sent messages + // will be a subset of. Receivers will check that the content + // match the expected content. + maxPayloadSize := 100000 + mainPayload := make([]byte, maxPayloadSize) + for i := 0; i < len(mainPayload); i++ { + mainPayload[i] = 'A' + byte(rand.Intn(26)) + } + for _, test := range []struct { + name string + compress bool + }{ + {"no_compression", false}, + {"with_compression", true}, + } { + t.Run(test.name, func(t *testing.T) { + sopts := testWSGetDefaultOptions(t, false) + sopts.Websocket.Compression = test.compress + s := RunServerWithOptions(sopts) + defer s.Shutdown() + + var count int64 + consDoneCh := make(chan struct{}, 1) + errCh := make(chan error, 1) + prodDoneCh := make(chan struct{}, prods) + + pushErr := func(e error) { + select { + case errCh <- e: + default: + } + } + + createConn := func() *nats.Conn { + t.Helper() + nc, err := nats.Connect(fmt.Sprintf("ws://127.0.0.1:%d", sopts.Websocket.Port), + nats.Compression(test.compress), + nats.ErrorHandler(func(_ *nats.Conn, sub *nats.Subscription, err error) { + if sub != nil { + err = fmt.Errorf("Subscription on %q - err=%v", sub.Subject, err) + } + pushErr(err) + })) + if err != nil { + t.Fatalf("Error connecting: %v", err) + } + return nc + } + + cb := func(m *nats.Msg) { + if len(m.Data) < 4 { + pushErr(fmt.Errorf("Message payload too small: %+v", m.Data)) + return + } + ps := int(binary.BigEndian.Uint32(m.Data[:4])) + if ps > maxPayloadSize { + pushErr(fmt.Errorf("Invalid message size: %v", ps)) + return + } + if !bytes.Equal(m.Data[4:4+ps], mainPayload[:ps]) { + pushErr(fmt.Errorf("invalid content")) + return + } + if atomic.AddInt64(&count, 1) == totalRecv { + consDoneCh <- struct{}{} + } + } + + subjects := []string{"foo", "bar"} + for _, subj := range subjects { + for i := 0; i < 2; i++ { + nc := createConn() + defer nc.Close() + sub, err := nc.Subscribe(subj, cb) + if err != nil { + t.Fatalf("Error on subscribe: %v", err) + } + sub.SetPendingLimits(-1, -1) + if err := nc.Flush(); err != nil { + t.Fatalf("Error on flush: %v", err) + } + } + } + + msgsPerProd := int(total / int64(prods)) + prodPerSubj := prods / len(subjects) + for _, subj := range subjects { + for i := 0; i < prodPerSubj; i++ { + go func(subj string) { + defer func() { prodDoneCh <- struct{}{} }() + + nc := createConn() + defer nc.Close() + + for i := 0; i < msgsPerProd; i++ { + // Have 80% of messages being rather small (<=1024) + maxSize := 1024 + if rand.Intn(100) > 80 { + maxSize = maxPayloadSize + } + ps := rand.Intn(maxSize) + msg := make([]byte, 4+ps) + binary.BigEndian.PutUint32(msg, uint32(ps)) + copy(msg[4:], mainPayload[:ps]) + if err := nc.Publish(subj, msg); err != nil { + pushErr(err) + return + } + } + nc.Flush() + }(subj) + } + } + + for i := 0; i < prods; i++ { + select { + case <-prodDoneCh: + case e := <-errCh: + t.Fatal(e) + } + } + // Now wait for all consumers to be done. + <-consDoneCh + }) + } +} + +func TestWSNoDeadlockOnAuthFailure(t *testing.T) { + o := testWSGetDefaultOptions(t, false) + o.Username = "user" + o.Password = "pwd" + s := RunServerWithOptions(o) + defer s.Shutdown() + + tm := time.AfterFunc(3*time.Second, func() { + buf := make([]byte, 1000000) + n := runtime.Stack(buf, true) + panic(fmt.Sprintf("Test has probably deadlocked!\n%s\n", buf[:n])) + }) + + if _, err := nats.Connect(fmt.Sprintf("ws://127.0.0.1:%d", o.Websocket.Port)); err == nil { + t.Fatal("Expected auth error, did not get any error") + } + + tm.Stop() +} diff --git a/testing_internal.go b/testing_internal.go new file mode 100644 index 000000000..18397026a --- /dev/null +++ b/testing_internal.go @@ -0,0 +1,59 @@ +// Copyright 2023 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build internal_testing +// +build internal_testing + +// Functions in this file are only available when building nats.go with the +// internal_testing build tag. They are used by the nats.go test suite. +package nats + +// AddMsgFilter adds a message filter for the given subject +// to the connection. The filter will be called for each +// message received on the subject. If the filter returns +// nil, the message will be dropped. +func (nc *Conn) AddMsgFilter(subject string, filter msgFilter) { + nc.subsMu.Lock() + defer nc.subsMu.Unlock() + + if nc.filters == nil { + nc.filters = make(map[string]msgFilter) + } + nc.filters[subject] = filter +} + +// RemoveMsgFilter removes a message filter for the given subject. +func (nc *Conn) RemoveMsgFilter(subject string) { + nc.subsMu.Lock() + defer nc.subsMu.Unlock() + + if nc.filters != nil { + delete(nc.filters, subject) + if len(nc.filters) == 0 { + nc.filters = nil + } + } +} + +// IsJSControlMessage returns true if the message is a JetStream control message. +func IsJSControlMessage(msg *Msg) (bool, int) { + return isJSControlMessage(msg) +} + +// CloseTCPConn closes the underlying TCP connection. +// It can be used to simulate a disconnect. +func (nc *Conn) CloseTCPConn() { + nc.mu.Lock() + defer nc.mu.Unlock() + nc.conn.Close() +} diff --git a/ws_test.go b/ws_test.go index 9773cabb8..2227c82af 100644 --- a/ws_test.go +++ b/ws_test.go @@ -16,50 +16,19 @@ package nats import ( "bytes" "context" - "crypto/tls" - "encoding/binary" "fmt" "io" - "math/rand" "net" "net/http" "reflect" - "runtime" "strings" "sync" - "sync/atomic" "testing" "time" "github.com/klauspost/compress/flate" - "github.com/nats-io/nats-server/v2/server" - serverTest "github.com/nats-io/nats-server/v2/test" - "github.com/nats-io/nuid" ) -func testWSGetDefaultOptions(t *testing.T, tls bool) *server.Options { - t.Helper() - sopts := serverTest.DefaultTestOptions - sopts.Host = "127.0.0.1" - sopts.Port = -1 - sopts.Websocket.Host = "127.0.0.1" - sopts.Websocket.Port = -1 - sopts.Websocket.NoTLS = !tls - if tls { - tc := &server.TLSConfigOpts{ - CertFile: "./test/configs/certs/server.pem", - KeyFile: "./test/configs/certs/key.pem", - CaFile: "./test/configs/certs/ca.pem", - } - tlsConfig, err := server.GenTLSConfig(tc) - if err != nil { - t.Fatalf("Can't build TLCConfig: %v", err) - } - sopts.Websocket.TLSConfig = tlsConfig - } - return &sopts -} - type fakeReader struct { mu sync.Mutex buf bytes.Buffer @@ -521,244 +490,6 @@ func TestWSNoMixingScheme(t *testing.T) { } } -func TestWSBasic(t *testing.T) { - sopts := testWSGetDefaultOptions(t, false) - s := RunServerWithOptions(sopts) - defer s.Shutdown() - - url := fmt.Sprintf("ws://127.0.0.1:%d", sopts.Websocket.Port) - nc, err := Connect(url) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer nc.Close() - - sub, err := nc.SubscribeSync("foo") - if err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - - msgs := make([][]byte, 100) - for i := 0; i < len(msgs); i++ { - msg := make([]byte, rand.Intn(70000)) - for j := 0; j < len(msg); j++ { - msg[j] = 'A' + byte(rand.Intn(26)) - } - msgs[i] = msg - } - for i, msg := range msgs { - if err := nc.Publish("foo", msg); err != nil { - t.Fatalf("Error on publish: %v", err) - } - // Make sure that masking does not overwrite user data - if !bytes.Equal(msgs[i], msg) { - t.Fatalf("User content has been changed: %v, got %v", msgs[i], msg) - } - } - - for i := 0; i < len(msgs); i++ { - msg, err := sub.NextMsg(time.Second) - if err != nil { - t.Fatalf("Error getting next message: %v", err) - } - if !bytes.Equal(msgs[i], msg.Data) { - t.Fatalf("Expected message: %v, got %v", msgs[i], msg) - } - } -} - -func TestWSControlFrames(t *testing.T) { - sopts := testWSGetDefaultOptions(t, false) - s := RunServerWithOptions(sopts) - defer s.Shutdown() - - rch := make(chan bool, 10) - ncSub, err := Connect(s.ClientURL(), - ReconnectWait(50*time.Millisecond), - ReconnectHandler(func(_ *Conn) { rch <- true }), - ) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer ncSub.Close() - - sub, err := ncSub.SubscribeSync("foo") - if err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - if err := ncSub.Flush(); err != nil { - t.Fatalf("Error on flush: %v", err) - } - - dch := make(chan error, 10) - url := fmt.Sprintf("ws://127.0.0.1:%d", sopts.Websocket.Port) - nc, err := Connect(url, - ReconnectWait(50*time.Millisecond), - DisconnectErrHandler(func(_ *Conn, err error) { dch <- err }), - ReconnectHandler(func(_ *Conn) { rch <- true }), - ) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer nc.Close() - - // Enqueue a PING and make sure that we don't break - nc.wsEnqueueControlMsg(true, wsPingMessage, []byte("this is a ping payload")) - select { - case e := <-dch: - t.Fatal(e) - case <-time.After(250 * time.Millisecond): - // OK - } - - // Shutdown the server, which should send a close message, which by - // spec the client will try to echo back. - s.Shutdown() - - select { - case <-dch: - // OK - case <-time.After(time.Second): - t.Fatal("Should have been disconnected") - } - - s = RunServerWithOptions(sopts) - defer s.Shutdown() - - // Wait for both connections to reconnect - if err := Wait(rch); err != nil { - t.Fatalf("Should have reconnected: %v", err) - } - if err := Wait(rch); err != nil { - t.Fatalf("Should have reconnected: %v", err) - } - // Even if the first connection reconnects, there is no guarantee - // that the resend of SUB has yet been processed by the server. - // Doing a flush here will give us the guarantee. - if err := ncSub.Flush(); err != nil { - t.Fatalf("Error on flush: %v", err) - } - - // Publish and close connection. - if err := nc.Publish("foo", []byte("msg")); err != nil { - t.Fatalf("Error on publish: %v", err) - } - if err := nc.Flush(); err != nil { - t.Fatalf("Error on flush: %v", err) - } - nc.Close() - - if _, err := sub.NextMsg(time.Second); err != nil { - t.Fatalf("Did not get message: %v", err) - } -} - -func TestWSConcurrentConns(t *testing.T) { - sopts := testWSGetDefaultOptions(t, false) - s := RunServerWithOptions(sopts) - defer s.Shutdown() - - url := fmt.Sprintf("ws://127.0.0.1:%d", sopts.Websocket.Port) - - total := 50 - errCh := make(chan error, total) - wg := sync.WaitGroup{} - wg.Add(total) - for i := 0; i < total; i++ { - go func() { - defer wg.Done() - - nc, err := Connect(url) - if err != nil { - errCh <- fmt.Errorf("Error on connect: %v", err) - return - } - defer nc.Close() - - sub, err := nc.SubscribeSync(nuid.Next()) - if err != nil { - errCh <- fmt.Errorf("Error on subscribe: %v", err) - return - } - nc.Publish(sub.Subject, []byte("here")) - if _, err := sub.NextMsg(time.Second); err != nil { - errCh <- err - } - }() - } - wg.Wait() - select { - case e := <-errCh: - t.Fatal(e.Error()) - default: - } -} - -func TestWSCompression(t *testing.T) { - msgSize := rand.Intn(40000) - for _, test := range []struct { - name string - srvCompression bool - cliCompression bool - }{ - {"srv_off_cli_off", false, false}, - {"srv_off_cli_on", false, true}, - {"srv_on_cli_off", true, false}, - {"srv_on_cli_on", true, true}, - } { - t.Run(test.name, func(t *testing.T) { - sopts := testWSGetDefaultOptions(t, false) - sopts.Websocket.Compression = test.srvCompression - s := RunServerWithOptions(sopts) - defer s.Shutdown() - - url := fmt.Sprintf("ws://127.0.0.1:%d", sopts.Websocket.Port) - var opts []Option - if test.cliCompression { - opts = append(opts, Compression(true)) - } - nc, err := Connect(url, opts...) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer nc.Close() - - sub, err := nc.SubscribeSync("foo") - if err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - - msgs := make([][]byte, 100) - for i := 0; i < len(msgs); i++ { - msg := make([]byte, msgSize) - for j := 0; j < len(msg); j++ { - msg[j] = 'A' - } - msgs[i] = msg - } - for i, msg := range msgs { - if err := nc.Publish("foo", msg); err != nil { - t.Fatalf("Error on publish: %v", err) - } - // Make sure that compression/masking does not touch user data - if !bytes.Equal(msgs[i], msg) { - t.Fatalf("User content has been changed: %v, got %v", msgs[i], msg) - } - } - - for i := 0; i < len(msgs); i++ { - msg, err := sub.NextMsg(time.Second) - if err != nil { - t.Fatalf("Error getting next message (%d): %v", i+1, err) - } - if !bytes.Equal(msgs[i], msg.Data) { - t.Fatalf("Expected message (%d): %v, got %v", i+1, msgs[i], msg) - } - } - }) - } -} - func TestWSCompressionWithContinuationFrames(t *testing.T) { uncompressed := []byte("this is an uncompressed message with AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") buf := &bytes.Buffer{} @@ -801,127 +532,6 @@ func TestWSCompressionWithContinuationFrames(t *testing.T) { } } -func TestWSWithTLS(t *testing.T) { - for _, test := range []struct { - name string - compression bool - }{ - {"without compression", false}, - {"with compression", true}, - } { - t.Run(test.name, func(t *testing.T) { - sopts := testWSGetDefaultOptions(t, true) - sopts.Websocket.Compression = test.compression - s := RunServerWithOptions(sopts) - defer s.Shutdown() - - var copts []Option - if test.compression { - copts = append(copts, Compression(true)) - } - - // Check that we fail to connect without proper TLS configuration. - nc, err := Connect(fmt.Sprintf("ws://localhost:%d", sopts.Websocket.Port), copts...) - if err == nil { - if nc != nil { - nc.Close() - } - t.Fatal("Expected error, got none") - } - - // Same but with wss protocol, which should translate to TLS, however, - // since we used self signed certificates, this should fail without - // asking to skip server cert verification. - nc, err = Connect(fmt.Sprintf("wss://localhost:%d", sopts.Websocket.Port), copts...) - // Since Go 1.18, we had to regenerate certs to not have to use GODEBUG="x509sha1=1" - // But on macOS, with our test CA certs, no SCTs included, it will fail - // for the reason "x509: “localhost” certificate is not standards compliant" - // instead of "unknown authority". - if err == nil || (!strings.Contains(err.Error(), "authority") && !strings.Contains(err.Error(), "compliant")) { - if nc != nil { - nc.Close() - } - t.Fatalf("Expected error about unknown authority: %v", err) - } - - // Skip server verification and we should be good. - copts = append(copts, Secure(&tls.Config{InsecureSkipVerify: true})) - nc, err = Connect(fmt.Sprintf("wss://localhost:%d", sopts.Websocket.Port), copts...) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer nc.Close() - - sub, err := nc.SubscribeSync("foo") - if err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - if err := nc.Publish("foo", []byte("hello")); err != nil { - t.Fatalf("Error on publish: %v", err) - } - if msg, err := sub.NextMsg(time.Second); err != nil { - t.Fatalf("Did not get message: %v", err) - } else if got := string(msg.Data); got != "hello" { - t.Fatalf("Expected %q, got %q", "hello", got) - } - }) - } -} - -type testSkipTLSDialer struct { - dialer *net.Dialer - skipTLS bool -} - -func (sd *testSkipTLSDialer) Dial(network, address string) (net.Conn, error) { - return sd.dialer.Dial(network, address) -} - -func (sd *testSkipTLSDialer) SkipTLSHandshake() bool { - return sd.skipTLS -} - -func TestWSWithTLSCustomDialer(t *testing.T) { - sopts := testWSGetDefaultOptions(t, true) - s := RunServerWithOptions(sopts) - defer s.Shutdown() - - sd := &testSkipTLSDialer{ - dialer: &net.Dialer{ - Timeout: 2 * time.Second, - }, - skipTLS: true, - } - - // Connect with CustomDialer that fails since TLSHandshake is disabled. - copts := make([]Option, 0) - copts = append(copts, Secure(&tls.Config{InsecureSkipVerify: true})) - copts = append(copts, SetCustomDialer(sd)) - _, err := Connect(fmt.Sprintf("wss://localhost:%d", sopts.Websocket.Port), copts...) - if err == nil { - t.Fatalf("Expected error on connect: %v", err) - } - if err.Error() != `invalid websocket connection` { - t.Logf("Expected invalid websocket connection: %v", err) - } - - // Retry with the dialer. - copts = make([]Option, 0) - sd = &testSkipTLSDialer{ - dialer: &net.Dialer{ - Timeout: 2 * time.Second, - }, - skipTLS: false, - } - copts = append(copts, Secure(&tls.Config{InsecureSkipVerify: true})) - copts = append(copts, SetCustomDialer(sd)) - nc, err := Connect(fmt.Sprintf("wss://localhost:%d", sopts.Websocket.Port), copts...) - if err != nil { - t.Fatalf("Unexpected error on connect: %v", err) - } - defer nc.Close() -} - func TestWSTlsNoConfig(t *testing.T) { opts := GetDefaultOptions() opts.Servers = []string{"wss://localhost:443"} @@ -952,224 +562,6 @@ func TestWSTlsNoConfig(t *testing.T) { nc.mu.Unlock() } -func TestWSGossipAndReconnect(t *testing.T) { - o1 := testWSGetDefaultOptions(t, false) - o1.ServerName = "A" - o1.Cluster.Host = "127.0.0.1" - o1.Cluster.Name = "abc" - o1.Cluster.Port = -1 - s1 := RunServerWithOptions(o1) - defer s1.Shutdown() - - o2 := testWSGetDefaultOptions(t, false) - o2.ServerName = "B" - o2.Cluster.Host = "127.0.0.1" - o2.Cluster.Name = "abc" - o2.Cluster.Port = -1 - o2.Routes = server.RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", o1.Cluster.Port)) - s2 := RunServerWithOptions(o2) - defer s2.Shutdown() - - rch := make(chan bool, 10) - url := fmt.Sprintf("ws://127.0.0.1:%d", o1.Websocket.Port) - nc, err := Connect(url, - ReconnectWait(50*time.Millisecond), - ReconnectHandler(func(_ *Conn) { rch <- true }), - ) - if err != nil { - t.Fatalf("Error on connect: %v", err) - } - defer nc.Close() - - timeout := time.Now().Add(time.Second) - for time.Now().Before(timeout) { - if len(nc.Servers()) > 1 { - break - } - time.Sleep(15 * time.Millisecond) - } - if len(nc.Servers()) == 1 { - t.Fatal("Did not discover server 2") - } - s1.Shutdown() - - // Wait for reconnect - if err := Wait(rch); err != nil { - t.Fatalf("Did not reconnect: %v", err) - } - - // Now check that connection is still WS - nc.mu.Lock() - isWS := nc.ws - _, ok := nc.bw.w.(*websocketWriter) - nc.mu.Unlock() - - if !isWS { - t.Fatal("Connection is not marked as websocket") - } - if !ok { - t.Fatal("Connection writer is not websocket") - } -} - -func TestWSStress(t *testing.T) { - // Enable this test only when wanting to stress test the system, say after - // some changes in the library or if a bug is found. Also, don't run it - // with the `-race` flag! - t.SkipNow() - // Total producers (there will be 2 per subject) - prods := 4 - // Total messages sent - total := int64(1000000) - // Total messages received, there is 2 consumer per subject - totalRecv := 2 * total - // We will create a "golden" slice from which sent messages - // will be a subset of. Receivers will check that the content - // match the expected content. - maxPayloadSize := 100000 - mainPayload := make([]byte, maxPayloadSize) - for i := 0; i < len(mainPayload); i++ { - mainPayload[i] = 'A' + byte(rand.Intn(26)) - } - for _, test := range []struct { - name string - compress bool - }{ - {"no_compression", false}, - {"with_compression", true}, - } { - t.Run(test.name, func(t *testing.T) { - sopts := testWSGetDefaultOptions(t, false) - sopts.Websocket.Compression = test.compress - s := RunServerWithOptions(sopts) - defer s.Shutdown() - - var count int64 - consDoneCh := make(chan struct{}, 1) - errCh := make(chan error, 1) - prodDoneCh := make(chan struct{}, prods) - - pushErr := func(e error) { - select { - case errCh <- e: - default: - } - } - - createConn := func() *Conn { - t.Helper() - nc, err := Connect(fmt.Sprintf("ws://127.0.0.1:%d", sopts.Websocket.Port), - Compression(test.compress), - ErrorHandler(func(_ *Conn, sub *Subscription, err error) { - if sub != nil { - err = fmt.Errorf("Subscription on %q - err=%v", sub.Subject, err) - } - pushErr(err) - })) - if err != nil { - t.Fatalf("Error connecting: %v", err) - } - return nc - } - - cb := func(m *Msg) { - if len(m.Data) < 4 { - pushErr(fmt.Errorf("Message payload too small: %+v", m.Data)) - return - } - ps := int(binary.BigEndian.Uint32(m.Data[:4])) - if ps > maxPayloadSize { - pushErr(fmt.Errorf("Invalid message size: %v", ps)) - return - } - if !bytes.Equal(m.Data[4:4+ps], mainPayload[:ps]) { - pushErr(fmt.Errorf("invalid content")) - return - } - if atomic.AddInt64(&count, 1) == totalRecv { - consDoneCh <- struct{}{} - } - } - - subjects := []string{"foo", "bar"} - for _, subj := range subjects { - for i := 0; i < 2; i++ { - nc := createConn() - defer nc.Close() - sub, err := nc.Subscribe(subj, cb) - if err != nil { - t.Fatalf("Error on subscribe: %v", err) - } - sub.SetPendingLimits(-1, -1) - if err := nc.Flush(); err != nil { - t.Fatalf("Error on flush: %v", err) - } - } - } - - msgsPerProd := int(total / int64(prods)) - prodPerSubj := prods / len(subjects) - for _, subj := range subjects { - for i := 0; i < prodPerSubj; i++ { - go func(subj string) { - defer func() { prodDoneCh <- struct{}{} }() - - nc := createConn() - defer nc.Close() - - for i := 0; i < msgsPerProd; i++ { - // Have 80% of messages being rather small (<=1024) - maxSize := 1024 - if rand.Intn(100) > 80 { - maxSize = maxPayloadSize - } - ps := rand.Intn(maxSize) - msg := make([]byte, 4+ps) - binary.BigEndian.PutUint32(msg, uint32(ps)) - copy(msg[4:], mainPayload[:ps]) - if err := nc.Publish(subj, msg); err != nil { - pushErr(err) - return - } - } - nc.Flush() - }(subj) - } - } - - for i := 0; i < prods; i++ { - select { - case <-prodDoneCh: - case e := <-errCh: - t.Fatal(e) - } - } - // Now wait for all consumers to be done. - <-consDoneCh - }) - } -} - -func TestWSNoDeadlockOnAuthFailure(t *testing.T) { - o := testWSGetDefaultOptions(t, false) - o.Username = "user" - o.Password = "pwd" - s := RunServerWithOptions(o) - defer s.Shutdown() - - tm := time.AfterFunc(3*time.Second, func() { - buf := make([]byte, 1000000) - n := runtime.Stack(buf, true) - panic(fmt.Sprintf("Test has probably deadlocked!\n%s\n", buf[:n])) - }) - - if _, err := Connect(fmt.Sprintf("ws://127.0.0.1:%d", o.Websocket.Port)); err == nil { - t.Fatal("Expected auth error, did not get any error") - } - - tm.Stop() -} - func TestWSProxyPath(t *testing.T) { const proxyPath = "proxy1"