mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
This addresses the following race: - client connection creates a subscription on a reply subject - client connection sends a request - server sends the subscription to inbound gateway - server sends the message to outbound gateway (those may be to different servers) - receiving server sends to sub interested in request subject - app sends reply - its server then check for interest on the reply's subject In interestOnly mode, there is a possibility that this server has not received the interest on the reply subject yet and would then drop the reply. This PR detects above scenario and will prefix the reply subject to identify the origin cluster if it is detected that the last subscription from the sending connection was created less than a second ago. Once the destination has this prefix, the destination cluster will always send back that message to origin cluster even if there is no registered interest. Signed-off-by: Ivan Kozlovic <ivan@synadia.com>
449 lines
13 KiB
Go
449 lines
13 KiB
Go
// Copyright 2018 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 (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"net"
|
|
"regexp"
|
|
"testing"
|
|
|
|
"github.com/nats-io/gnatsd/server"
|
|
)
|
|
|
|
func testDefaultOptionsForGateway(name string) *server.Options {
|
|
o := DefaultTestOptions
|
|
o.Port = -1
|
|
o.Gateway.Name = name
|
|
o.Gateway.Host = "127.0.0.1"
|
|
o.Gateway.Port = -1
|
|
return &o
|
|
}
|
|
|
|
func runGatewayServer(o *server.Options) *server.Server {
|
|
s := RunServer(o)
|
|
return s
|
|
}
|
|
|
|
func createGatewayConn(t testing.TB, host string, port int) net.Conn {
|
|
t.Helper()
|
|
return createClientConn(t, host, port)
|
|
}
|
|
|
|
func setupGatewayConn(t testing.TB, c net.Conn, org, dst string) (sendFun, expectFun) {
|
|
t.Helper()
|
|
dstInfo := checkInfoMsg(t, c)
|
|
if dstInfo.Gateway != dst {
|
|
t.Fatalf("Expected to connect to %q, got %q", dst, dstInfo.Gateway)
|
|
}
|
|
cs := fmt.Sprintf("CONNECT {\"verbose\":%v,\"pedantic\":%v,\"tls_required\":%v,\"gateway\":%q}\r\n",
|
|
false, false, false, org)
|
|
sendProto(t, c, cs)
|
|
sendProto(t, c, fmt.Sprintf("INFO {\"gateway\":%q}\r\n", org))
|
|
return sendCommand(t, c), expectCommand(t, c)
|
|
}
|
|
|
|
func expectNumberOfProtos(t *testing.T, expFn expectFun, proto *regexp.Regexp, expected int) {
|
|
t.Helper()
|
|
for count := 0; count != expected; {
|
|
buf := expFn(proto)
|
|
count += len(proto.FindAllSubmatch(buf, -1))
|
|
if count > expected {
|
|
t.Fatalf("Expected %v matches, got %v", expected, count)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGatewayAccountInterest(t *testing.T) {
|
|
ob := testDefaultOptionsForGateway("B")
|
|
sb := runGatewayServer(ob)
|
|
defer sb.Shutdown()
|
|
|
|
gA := createGatewayConn(t, ob.Gateway.Host, ob.Gateway.Port)
|
|
defer gA.Close()
|
|
|
|
gASend, gAExpect := setupGatewayConn(t, gA, "A", "B")
|
|
gASend("PING\r\n")
|
|
gAExpect(pongRe)
|
|
|
|
// Sending a bunch of messages. On the first, "B" will send an A-
|
|
// protocol.
|
|
for i := 0; i < 100; i++ {
|
|
gASend("RMSG $foo foo 2\r\nok\r\n")
|
|
}
|
|
// We expect single A- followed by PONG. If "B" was sending more
|
|
// this expect call would fail.
|
|
gAExpect(aunsubRe)
|
|
gASend("PING\r\n")
|
|
gAExpect(pongRe)
|
|
|
|
// Start gateway C that connects to B
|
|
gC := createGatewayConn(t, ob.Gateway.Host, ob.Gateway.Port)
|
|
defer gC.Close()
|
|
|
|
gCSend, gCExpect := setupGatewayConn(t, gC, "C", "B")
|
|
gCSend("PING\r\n")
|
|
gCExpect(pongRe)
|
|
// Send more messages, C should get A-, but A should not (already
|
|
// got it).
|
|
for i := 0; i < 100; i++ {
|
|
gCSend("RMSG $foo foo 2\r\nok\r\n")
|
|
}
|
|
gCExpect(aunsubRe)
|
|
gCSend("PING\r\n")
|
|
gCExpect(pongRe)
|
|
expectNothing(t, gA)
|
|
|
|
// Restart one of the gateway, and resend a message, verify
|
|
// that it receives A- (things get cleared on reconnect)
|
|
gC.Close()
|
|
gC = createGatewayConn(t, ob.Gateway.Host, ob.Gateway.Port)
|
|
defer gC.Close()
|
|
|
|
gCSend, gCExpect = setupGatewayConn(t, gC, "C", "B")
|
|
gCSend("PING\r\n")
|
|
gCExpect(pongRe)
|
|
gCSend("RMSG $foo foo 2\r\nok\r\n")
|
|
gCExpect(aunsubRe)
|
|
gCSend("PING\r\n")
|
|
gCExpect(pongRe)
|
|
expectNothing(t, gA)
|
|
|
|
// Close again and re-create, but this time don't send anything.
|
|
gC.Close()
|
|
gC = createGatewayConn(t, ob.Gateway.Host, ob.Gateway.Port)
|
|
defer gC.Close()
|
|
|
|
gCSend, gCExpect = setupGatewayConn(t, gC, "C", "B")
|
|
gCSend("PING\r\n")
|
|
gCExpect(pongRe)
|
|
|
|
// Now register the $foo account on B and create a subscription,
|
|
// A should receive an A+ because B knows that it previously sent
|
|
// an A-, but since it did not send one to C, C should not receive
|
|
// the A+.
|
|
sb.RegisterAccount("$foo")
|
|
client := createClientConn(t, ob.Host, ob.Port)
|
|
defer client.Close()
|
|
clientSend, clientExpect := setupConnWithAccount(t, client, "$foo")
|
|
clientSend("SUB not.used 1234567\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
gAExpect(asubRe)
|
|
expectNothing(t, gC)
|
|
}
|
|
|
|
func TestGatewaySubjectInterest(t *testing.T) {
|
|
ob := testDefaultOptionsForGateway("B")
|
|
fooAcc := server.NewAccount("$foo")
|
|
ob.Accounts = []*server.Account{fooAcc}
|
|
ob.Users = []*server.User{&server.User{Username: "ivan", Password: "password", Account: fooAcc}}
|
|
sb := runGatewayServer(ob)
|
|
defer sb.Shutdown()
|
|
|
|
// Create a client on B
|
|
client := createClientConn(t, ob.Host, ob.Port)
|
|
defer client.Close()
|
|
clientSend, clientExpect := setupConnWithUserPass(t, client, "ivan", "password")
|
|
// Since we want to test RS+/-, we need to have at
|
|
// least a subscription on B so that sending from A does
|
|
// not result in A-
|
|
clientSend("SUB not.used 1234567\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
|
|
gA := createGatewayConn(t, ob.Gateway.Host, ob.Gateway.Port)
|
|
defer gA.Close()
|
|
|
|
gASend, gAExpect := setupGatewayConn(t, gA, "A", "B")
|
|
gASend("PING\r\n")
|
|
gAExpect(pongRe)
|
|
|
|
for i := 0; i < 100; i++ {
|
|
gASend("RMSG $foo foo 2\r\nok\r\n")
|
|
}
|
|
// We expect single RS- followed by PONG. If "B" was sending more
|
|
// this expect call would fail.
|
|
gAExpect(runsubRe)
|
|
gASend("PING\r\n")
|
|
gAExpect(pongRe)
|
|
|
|
// Start gateway C that connects to B
|
|
gC := createGatewayConn(t, ob.Gateway.Host, ob.Gateway.Port)
|
|
defer gC.Close()
|
|
|
|
gCSend, gCExpect := setupGatewayConn(t, gC, "C", "B")
|
|
gCSend("PING\r\n")
|
|
gCExpect(pongRe)
|
|
// Send more messages, C should get RS-, but A should not (already
|
|
// got it).
|
|
for i := 0; i < 100; i++ {
|
|
gCSend("RMSG $foo foo 2\r\nok\r\n")
|
|
}
|
|
gCExpect(runsubRe)
|
|
gCSend("PING\r\n")
|
|
gCExpect(pongRe)
|
|
expectNothing(t, gA)
|
|
|
|
// Restart one of the gateway, and resend a message, verify
|
|
// that it receives RS- (things get cleared on reconnect)
|
|
gC.Close()
|
|
gC = createGatewayConn(t, ob.Gateway.Host, ob.Gateway.Port)
|
|
defer gC.Close()
|
|
|
|
gCSend, gCExpect = setupGatewayConn(t, gC, "C", "B")
|
|
gCSend("PING\r\n")
|
|
gCExpect(pongRe)
|
|
gCSend("RMSG $foo foo 2\r\nok\r\n")
|
|
gCExpect(runsubRe)
|
|
gCSend("PING\r\n")
|
|
gCExpect(pongRe)
|
|
expectNothing(t, gA)
|
|
|
|
// Close again and re-create, but this time don't send anything.
|
|
gC.Close()
|
|
gC = createGatewayConn(t, ob.Gateway.Host, ob.Gateway.Port)
|
|
defer gC.Close()
|
|
|
|
gCSend, gCExpect = setupGatewayConn(t, gC, "C", "B")
|
|
gCSend("PING\r\n")
|
|
gCExpect(pongRe)
|
|
|
|
// Now register a subscription on foo for account $foo on B.
|
|
// A should receive a RS+ because B knows that it previously
|
|
// sent a RS-, but since it did not send one to C, C should
|
|
// not receive the RS+.
|
|
clientSend("SUB foo 1\r\nSUB foo 2\r\n")
|
|
// Also subscribe to subject that was not used before,
|
|
// so there should be no RS+ for this one.
|
|
clientSend("SUB bar 3\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
|
|
gAExpect(rsubRe)
|
|
expectNothing(t, gC)
|
|
// Check that we get only one protocol
|
|
expectNothing(t, gA)
|
|
|
|
// Unsubscribe the 2 subs on foo, expect to receive nothing.
|
|
clientSend("UNSUB 1\r\nUNSUB 2\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
|
|
expectNothing(t, gC)
|
|
expectNothing(t, gA)
|
|
|
|
gC.Close()
|
|
|
|
// Send on foo, should get an RS-
|
|
gASend("RMSG $foo foo 2\r\nok\r\n")
|
|
gAExpect(runsubRe)
|
|
// Subscribe on foo, should get an RS+ that removes the no-interest
|
|
clientSend("SUB foo 4\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
gAExpect(rsubRe)
|
|
// Send on bar, message should be received.
|
|
gASend("RMSG $foo bar 2\r\nok\r\n")
|
|
clientExpect(msgRe)
|
|
// Unsub foo and bar
|
|
clientSend("UNSUB 3\r\nUNSUB 4\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
expectNothing(t, gA)
|
|
// Send on both foo and bar expect RS-
|
|
gASend("RMSG $foo foo 2\r\nok\r\n")
|
|
gAExpect(runsubRe)
|
|
gASend("RMSG $foo bar 2\r\nok\r\n")
|
|
gAExpect(runsubRe)
|
|
// Now have client create sub on "*", this should cause RS+ on *
|
|
// The remote will have cleared its no-interest on foo and bar
|
|
// and this receiving side is supposed to be doing the same.
|
|
clientSend("SUB * 5\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
buf := gAExpect(rsubRe)
|
|
if !bytes.Contains(buf, []byte("$foo *")) {
|
|
t.Fatalf("Expected RS+ on %q, got %q", "*", buf)
|
|
}
|
|
// Check that the remote has cleared by sending from the client
|
|
// on foo and bar
|
|
clientSend("PUB foo 2\r\nok\r\n")
|
|
clientExpect(msgRe)
|
|
clientSend("PUB bar 2\r\nok\r\n")
|
|
clientExpect(msgRe)
|
|
// Check that A can send too and does not receive an RS-
|
|
gASend("RMSG $foo foo 2\r\nok\r\n")
|
|
expectNothing(t, gA)
|
|
clientExpect(msgRe)
|
|
gASend("RMSG $foo bar 2\r\nok\r\n")
|
|
expectNothing(t, gA)
|
|
clientExpect(msgRe)
|
|
}
|
|
|
|
func TestGatewayQueue(t *testing.T) {
|
|
ob := testDefaultOptionsForGateway("B")
|
|
fooAcc := server.NewAccount("$foo")
|
|
ob.Accounts = []*server.Account{fooAcc}
|
|
ob.Users = []*server.User{&server.User{Username: "ivan", Password: "password", Account: fooAcc}}
|
|
sb := runGatewayServer(ob)
|
|
defer sb.Shutdown()
|
|
|
|
gA := createGatewayConn(t, ob.Gateway.Host, ob.Gateway.Port)
|
|
defer gA.Close()
|
|
|
|
gASend, gAExpect := setupGatewayConn(t, gA, "A", "B")
|
|
gASend("PING\r\n")
|
|
gAExpect(pongRe)
|
|
|
|
client := createClientConn(t, ob.Host, ob.Port)
|
|
defer client.Close()
|
|
clientSend, clientExpect := setupConnWithUserPass(t, client, "ivan", "password")
|
|
|
|
// Create one queue sub on foo.* for group bar.
|
|
clientSend("SUB foo.* bar 1\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
// Expect RS+
|
|
gAExpect(rsubRe)
|
|
// Add another queue sub on same group
|
|
clientSend("SUB foo.* bar 2\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
// Should not receive another RS+ for that one
|
|
expectNothing(t, gA)
|
|
// However, if subject is different, we can expect to receive another RS+
|
|
clientSend("SUB foo.> bar 3\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
gAExpect(rsubRe)
|
|
|
|
// Unsub one of the foo.* qsub, no RS- should be received
|
|
clientSend("UNSUB 1\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
expectNothing(t, gA)
|
|
// Remove the other one, now we should get the RS-
|
|
clientSend("UNSUB 2\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
gAExpect(runsubRe)
|
|
// Remove last one
|
|
clientSend("UNSUB 3\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
gAExpect(runsubRe)
|
|
|
|
// Create some queues and check that interest is sent
|
|
// when GW reconnects.
|
|
clientSend("SUB foo bar 4\r\n")
|
|
gAExpect(rsubRe)
|
|
clientSend("SUB foo baz 5\r\n")
|
|
gAExpect(rsubRe)
|
|
clientSend("SUB foo bat 6\r\n")
|
|
gAExpect(rsubRe)
|
|
// There is already one on foo/bar, so nothing sent
|
|
clientSend("SUB foo bar 7\r\n")
|
|
expectNothing(t, gA)
|
|
// Add regular sub that should not cause RS+
|
|
clientSend("SUB foo 8\r\n")
|
|
expectNothing(t, gA)
|
|
|
|
// Recreate gA
|
|
gA.Close()
|
|
gA = createGatewayConn(t, ob.Gateway.Host, ob.Gateway.Port)
|
|
defer gA.Close()
|
|
gASend, gAExpect = setupGatewayConn(t, gA, "A", "B")
|
|
// A should receive 3 RS+
|
|
expectNumberOfProtos(t, gAExpect, rsubRe, 3)
|
|
// Nothing more
|
|
expectNothing(t, gA)
|
|
gASend("PING\r\n")
|
|
gAExpect(pongRe)
|
|
|
|
// Have A send a message on subject that has no sub
|
|
gASend("RMSG $foo new.subject 2\r\nok\r\n")
|
|
gAExpect(runsubRe)
|
|
// Now create a queue sub and check that we do not receive
|
|
// an RS+ without the queue name.
|
|
clientSend("SUB new.* queue 9\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
buf := gAExpect(rsubRe)
|
|
if !bytes.Contains(buf, []byte("new.* queue")) {
|
|
t.Fatalf("Should have receives RS+ for new.* for queue, did not: %v", buf)
|
|
}
|
|
// Check for no other RS+. A should still keep an RS- for plain
|
|
// sub on new.subject
|
|
expectNothing(t, gA)
|
|
// Send message, expected to be received by client
|
|
gASend("RMSG $foo new.subject | queue 2\r\nok\r\n")
|
|
clientExpect(msgRe)
|
|
// Unsubscribe the queue sub
|
|
clientSend("UNSUB 9\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
// A should receive RS- for this queue sub
|
|
buf = gAExpect(runsubRe)
|
|
if !bytes.Contains(buf, []byte("new.* queue")) {
|
|
t.Fatalf("Should have receives RS- for new.* for queue, did not: %v", buf)
|
|
}
|
|
expectNothing(t, gA)
|
|
}
|
|
|
|
func TestGatewaySendAllSubs(t *testing.T) {
|
|
ob := testDefaultOptionsForGateway("B")
|
|
sb := runGatewayServer(ob)
|
|
defer sb.Shutdown()
|
|
|
|
// Create a client on B
|
|
client := createClientConn(t, ob.Host, ob.Port)
|
|
defer client.Close()
|
|
clientSend, clientExpect := setupConn(t, client)
|
|
// Since we want to test RS+/-, we need to have at
|
|
// least a subscription on B so that sending from A does
|
|
// not result in A-
|
|
clientSend("SUB not.used 1234567\r\nPING\r\n")
|
|
clientExpect(pongRe)
|
|
|
|
gA := createGatewayConn(t, ob.Gateway.Host, ob.Gateway.Port)
|
|
defer gA.Close()
|
|
|
|
gASend, gAExpect := setupGatewayConn(t, gA, "A", "B")
|
|
gASend("PING\r\n")
|
|
gAExpect(pongRe)
|
|
|
|
// Bombard B with messages on different subjects.
|
|
// TODO(ik): Adapt if/when we change the conditions for the
|
|
// switch.
|
|
for i := 0; i < 10001; i++ {
|
|
gASend(fmt.Sprintf("RMSG $G foo.%d 2\r\nok\r\n", i))
|
|
if i <= 1000 {
|
|
gAExpect(runsubRe)
|
|
}
|
|
}
|
|
// Expect an INFO + RS+ $G not.used + INFO
|
|
buf := bufio.NewReader(gA)
|
|
for i := 0; i < 3; i++ {
|
|
line, _, err := buf.ReadLine()
|
|
if err != nil {
|
|
t.Fatalf("Error reading: %v", err)
|
|
}
|
|
switch i {
|
|
case 0:
|
|
case 2:
|
|
if !bytes.HasPrefix(line, []byte("INFO {")) {
|
|
t.Fatalf("Expected INFO, got: %s", line)
|
|
}
|
|
case 1:
|
|
if !bytes.HasPrefix(line, []byte("RS+ ")) {
|
|
t.Fatalf("Expected RS+, got: %s", line)
|
|
}
|
|
}
|
|
}
|
|
// After this point, any new sub or unsub on B should be
|
|
// sent to A.
|
|
clientSend("SUB foo 1\r\n")
|
|
gAExpect(rsubRe)
|
|
clientSend("UNSUB 1\r\n")
|
|
gAExpect(runsubRe)
|
|
}
|