mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-17 03:24:40 -07:00
Properly support leadnode clusters.
Leafnodes that formed clusters were partially supported. This adds proper support for origin cluster, subscription suppression and data message no echo for the origin cluster. Signed-off-by: Derek Collison <derek@nats.io>
This commit is contained in:
@@ -3145,7 +3145,6 @@ func TestLeafNodeCycleWithSolicited(t *testing.T) {
|
||||
atomic.AddInt32(&requestsReceived, 1)
|
||||
m.Respond([]byte("22"))
|
||||
})
|
||||
nc.Flush()
|
||||
|
||||
nc = clientForCluster(t, cb)
|
||||
defer nc.Close()
|
||||
@@ -3153,12 +3152,35 @@ func TestLeafNodeCycleWithSolicited(t *testing.T) {
|
||||
atomic.AddInt32(&requestsReceived, 1)
|
||||
m.Respond([]byte("33"))
|
||||
})
|
||||
nc.Flush()
|
||||
|
||||
// Soliciting cluster, both solicited connected to the "A" cluster
|
||||
sc := runSolicitLeafCluster(t, "SC", ca, ca)
|
||||
defer shutdownCluster(sc)
|
||||
|
||||
checkInterest := func(s *server.Server, subject string) bool {
|
||||
t.Helper()
|
||||
acc, _ := s.LookupAccount("$G")
|
||||
return acc.SubscriptionInterest(subject)
|
||||
}
|
||||
|
||||
waitForInterest := func(subject string, servers ...*server.Server) {
|
||||
t.Helper()
|
||||
checkFor(t, time.Second, 10*time.Millisecond, func() error {
|
||||
for _, s := range servers {
|
||||
if !checkInterest(s, subject) {
|
||||
return fmt.Errorf("No interest")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
waitForInterest("request",
|
||||
sc.servers[0], sc.servers[1],
|
||||
ca.servers[0], ca.servers[1], ca.servers[2],
|
||||
cb.servers[0], cb.servers[1], cb.servers[2],
|
||||
)
|
||||
|
||||
// Connect a client to a random server in sc
|
||||
createClientAndRequest := func(c *cluster) (*nats.Conn, *nats.Subscription) {
|
||||
nc := clientForCluster(t, c)
|
||||
@@ -3762,3 +3784,237 @@ func TestLeafNodeQueueSubscriberUnsubscribe(t *testing.T) {
|
||||
// Make sure we receive nothing...
|
||||
expectNothing(t, lc)
|
||||
}
|
||||
|
||||
func TestLeafNodeOriginClusterSingleHub(t *testing.T) {
|
||||
s, opts := runLeafServer()
|
||||
defer s.Shutdown()
|
||||
|
||||
c1 := `
|
||||
listen: 127.0.0.1:-1
|
||||
cluster { name: ln22, listen: 127.0.0.1:-1 }
|
||||
leafnodes { remotes = [{ url: nats-leaf://127.0.0.1:%d }] }
|
||||
`
|
||||
lconf1 := createConfFile(t, []byte(fmt.Sprintf(c1, opts.LeafNode.Port)))
|
||||
defer os.Remove(lconf1)
|
||||
|
||||
ln1, lopts1 := RunServerWithConfig(lconf1)
|
||||
defer ln1.Shutdown()
|
||||
|
||||
c2 := `
|
||||
listen: 127.0.0.1:-1
|
||||
cluster { name: ln22, listen: 127.0.0.1:-1, routes = [ nats-route://127.0.0.1:%d] }
|
||||
leafnodes { remotes = [{ url: nats-leaf://127.0.0.1:%d }] }
|
||||
`
|
||||
lconf2 := createConfFile(t, []byte(fmt.Sprintf(c2, lopts1.Cluster.Port, opts.LeafNode.Port)))
|
||||
defer os.Remove(lconf2)
|
||||
|
||||
ln2, _ := RunServerWithConfig(lconf2)
|
||||
defer ln2.Shutdown()
|
||||
|
||||
ln3, _ := RunServerWithConfig(lconf2)
|
||||
defer ln3.Shutdown()
|
||||
|
||||
checkClusterFormed(t, ln1, ln2, ln3)
|
||||
checkLeafNodeConnections(t, s, 3)
|
||||
|
||||
// So now we are setup with 3 solicited leafnodes all connected to a hub.
|
||||
// We will create two clients, one on each leafnode server.
|
||||
nc1, err := nats.Connect(ln1.ClientURL())
|
||||
if err != nil {
|
||||
t.Fatalf("Error on connect: %v", err)
|
||||
}
|
||||
defer nc1.Close()
|
||||
|
||||
nc2, err := nats.Connect(ln2.ClientURL())
|
||||
if err != nil {
|
||||
t.Fatalf("Error on connect: %v", err)
|
||||
}
|
||||
defer nc2.Close()
|
||||
|
||||
checkInterest := func(s *server.Server, subject string) bool {
|
||||
t.Helper()
|
||||
acc, _ := s.LookupAccount("$G")
|
||||
return acc.SubscriptionInterest(subject)
|
||||
}
|
||||
|
||||
waitForInterest := func(subject string, servers ...*server.Server) {
|
||||
t.Helper()
|
||||
checkFor(t, time.Second, 10*time.Millisecond, func() error {
|
||||
for _, s := range servers {
|
||||
if !checkInterest(s, subject) {
|
||||
return fmt.Errorf("No interest")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
subj := "foo.bar"
|
||||
|
||||
sub, _ := nc2.SubscribeSync(subj)
|
||||
waitForInterest(subj, ln1, ln2, ln3, s)
|
||||
|
||||
// Make sure we truncated the subscription bouncing through the hub and back to other leafnodes.
|
||||
for _, s := range []*server.Server{ln1, ln3} {
|
||||
acc, _ := s.LookupAccount("$G")
|
||||
if nms := acc.Interest(subj); nms != 1 {
|
||||
t.Fatalf("Expected only one active subscription, got %d", nms)
|
||||
}
|
||||
}
|
||||
|
||||
// Send a message.
|
||||
nc1.Publish(subj, nil)
|
||||
nc1.Flush()
|
||||
// Wait to propagate
|
||||
time.Sleep(25 * time.Millisecond)
|
||||
|
||||
// Make sure we only get it once.
|
||||
if n, _, _ := sub.Pending(); n != 1 {
|
||||
t.Fatalf("Expected only one message, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLeafNodeOriginCluster(t *testing.T) {
|
||||
ca := createClusterWithName(t, "A", 3)
|
||||
defer shutdownCluster(ca)
|
||||
|
||||
c1 := `
|
||||
server_name: L1
|
||||
listen: 127.0.0.1:-1
|
||||
cluster { name: ln22, listen: 127.0.0.1:-1 }
|
||||
leafnodes { remotes = [{ url: nats-leaf://127.0.0.1:%d }] }
|
||||
`
|
||||
lconf1 := createConfFile(t, []byte(fmt.Sprintf(c1, ca.opts[0].LeafNode.Port)))
|
||||
defer os.Remove(lconf1)
|
||||
|
||||
ln1, lopts1 := RunServerWithConfig(lconf1)
|
||||
defer ln1.Shutdown()
|
||||
|
||||
c2 := `
|
||||
server_name: L2
|
||||
listen: 127.0.0.1:-1
|
||||
cluster { name: ln22, listen: 127.0.0.1:-1, routes = [ nats-route://127.0.0.1:%d] }
|
||||
leafnodes { remotes = [{ url: nats-leaf://127.0.0.1:%d }] }
|
||||
`
|
||||
lconf2 := createConfFile(t, []byte(fmt.Sprintf(c2, lopts1.Cluster.Port, ca.opts[1].LeafNode.Port)))
|
||||
defer os.Remove(lconf2)
|
||||
|
||||
ln2, _ := RunServerWithConfig(lconf2)
|
||||
defer ln2.Shutdown()
|
||||
|
||||
c3 := `
|
||||
server_name: L3
|
||||
listen: 127.0.0.1:-1
|
||||
cluster { name: ln22, listen: 127.0.0.1:-1, routes = [ nats-route://127.0.0.1:%d] }
|
||||
leafnodes { remotes = [{ url: nats-leaf://127.0.0.1:%d }] }
|
||||
`
|
||||
lconf3 := createConfFile(t, []byte(fmt.Sprintf(c3, lopts1.Cluster.Port, ca.opts[2].LeafNode.Port)))
|
||||
defer os.Remove(lconf3)
|
||||
|
||||
ln3, _ := RunServerWithConfig(lconf3)
|
||||
defer ln3.Shutdown()
|
||||
|
||||
checkClusterFormed(t, ln1, ln2, ln3)
|
||||
checkLeafNodeConnections(t, ca.servers[0], 1)
|
||||
checkLeafNodeConnections(t, ca.servers[1], 1)
|
||||
checkLeafNodeConnections(t, ca.servers[2], 1)
|
||||
|
||||
// So now we are setup with 3 solicited leafnodes connected to different servers in the hub cluster.
|
||||
// We will create two clients, one on each leafnode server.
|
||||
nc1, err := nats.Connect(ln1.ClientURL())
|
||||
if err != nil {
|
||||
t.Fatalf("Error on connect: %v", err)
|
||||
}
|
||||
defer nc1.Close()
|
||||
|
||||
nc2, err := nats.Connect(ln2.ClientURL())
|
||||
if err != nil {
|
||||
t.Fatalf("Error on connect: %v", err)
|
||||
}
|
||||
defer nc2.Close()
|
||||
|
||||
checkInterest := func(s *server.Server, subject string) bool {
|
||||
t.Helper()
|
||||
acc, _ := s.LookupAccount("$G")
|
||||
return acc.SubscriptionInterest(subject)
|
||||
}
|
||||
|
||||
waitForInterest := func(subject string, servers ...*server.Server) {
|
||||
t.Helper()
|
||||
checkFor(t, time.Second, 10*time.Millisecond, func() error {
|
||||
for _, s := range servers {
|
||||
if !checkInterest(s, subject) {
|
||||
return fmt.Errorf("No interest")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
subj := "foo.bar"
|
||||
|
||||
sub, _ := nc2.SubscribeSync(subj)
|
||||
waitForInterest(subj, ln1, ln2, ln3, ca.servers[0], ca.servers[1], ca.servers[2])
|
||||
|
||||
// Make sure we truncated the subscription bouncing through the hub and back to other leafnodes.
|
||||
for _, s := range []*server.Server{ln1, ln3} {
|
||||
acc, _ := s.LookupAccount("$G")
|
||||
if nms := acc.Interest(subj); nms != 1 {
|
||||
t.Fatalf("Expected only one active subscription, got %d", nms)
|
||||
}
|
||||
}
|
||||
|
||||
// Send a message.
|
||||
nc1.Publish(subj, nil)
|
||||
nc1.Flush()
|
||||
// Wait to propagate
|
||||
time.Sleep(25 * time.Millisecond)
|
||||
|
||||
// Make sure we only get it once.
|
||||
if n, _, _ := sub.Pending(); n != 1 {
|
||||
t.Fatalf("Expected only one message, got %d", n)
|
||||
}
|
||||
// eat the msg
|
||||
sub.NextMsg(time.Second)
|
||||
|
||||
// Now create interest on the hub side. This will draw the message from a leafnode
|
||||
// to the hub. We want to make sure that message does not bounce back to other leafnodes.
|
||||
nc3, err := nats.Connect(ca.servers[0].ClientURL())
|
||||
if err != nil {
|
||||
t.Fatalf("Error on connect: %v", err)
|
||||
}
|
||||
defer nc3.Close()
|
||||
|
||||
wcSubj := "foo.*"
|
||||
nc3.SubscribeSync(wcSubj)
|
||||
// This is a placeholder that we can use to check all interest has propagated.
|
||||
nc3.SubscribeSync("bar")
|
||||
waitForInterest("bar", ln1, ln2, ln3, ca.servers[0], ca.servers[1], ca.servers[2])
|
||||
|
||||
// Send another message.
|
||||
m := nats.NewMsg(subj)
|
||||
m.Header.Add("Accept-Encoding", "json")
|
||||
m.Header.Add("Authorization", "s3cr3t")
|
||||
m.Data = []byte("Hello Headers!")
|
||||
|
||||
nc1.PublishMsg(m)
|
||||
nc1.Flush()
|
||||
// Wait to propagate
|
||||
time.Sleep(25 * time.Millisecond)
|
||||
|
||||
// Make sure we only get it once.
|
||||
if n, _, _ := sub.Pending(); n != 1 {
|
||||
t.Fatalf("Expected only one message, got %d", n)
|
||||
}
|
||||
// grab the msg
|
||||
msg, _ := sub.NextMsg(time.Second)
|
||||
if !bytes.Equal(m.Data, msg.Data) {
|
||||
t.Fatalf("Expected the payloads to match, wanted %q, got %q", m.Data, msg.Data)
|
||||
}
|
||||
if len(msg.Header) != 2 {
|
||||
t.Fatalf("Expected 2 header entries, got %d", len(msg.Header))
|
||||
}
|
||||
if msg.Header.Get("Authorization") != "s3cr3t" {
|
||||
t.Fatalf("Expected auth header to match, wanted %q, got %q", "s3cr3t", msg.Header.Get("Authorization"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -54,6 +55,10 @@ func TestNewRouteInfoOnConnect(t *testing.T) {
|
||||
if !info.Headers {
|
||||
t.Fatalf("Expected to have headers on by default")
|
||||
}
|
||||
// Leafnode origin cluster support.
|
||||
if !info.LNOC {
|
||||
t.Fatalf("Expected to have leafnode origin cluster support")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRouteHeaderSupport(t *testing.T) {
|
||||
@@ -825,7 +830,7 @@ func TestNewRouteProcessRoutedMsgs(t *testing.T) {
|
||||
matches := expectMsgs(1)
|
||||
checkMsg(t, matches[0], "foo", "1", "", "2", "ok")
|
||||
|
||||
// Now send in a RMSG to the route witha reply and make sure its delivered to the client.
|
||||
// Now send in a RMSG to the route with a reply and make sure its delivered to the client.
|
||||
routeSend("RMSG $G foo reply 2\r\nok\r\nPING\r\n")
|
||||
routeExpect(pongRe)
|
||||
|
||||
@@ -1715,3 +1720,116 @@ func TestNewRouteLargeDistinctQueueSubscribers(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewRouteLeafNodeOriginSupport(t *testing.T) {
|
||||
content := `
|
||||
listen: 127.0.0.1:-1
|
||||
cluster { name: xyz, listen: 127.0.0.1:-1 }
|
||||
leafnodes { listen: 127.0.0.1:-1 }
|
||||
no_sys_acc: true
|
||||
`
|
||||
conf := createConfFile(t, []byte(content))
|
||||
defer os.Remove(conf)
|
||||
|
||||
s, opts := RunServerWithConfig(conf)
|
||||
defer s.Shutdown()
|
||||
|
||||
gacc, _ := s.LookupAccount("$G")
|
||||
|
||||
lcontent := `
|
||||
listen: 127.0.0.1:-1
|
||||
cluster { name: ln1, listen: 127.0.0.1:-1 }
|
||||
leafnodes { remotes = [{ url: nats-leaf://127.0.0.1:%d }] }
|
||||
no_sys_acc: true
|
||||
`
|
||||
lconf := createConfFile(t, []byte(fmt.Sprintf(lcontent, opts.LeafNode.Port)))
|
||||
defer os.Remove(lconf)
|
||||
|
||||
ln, _ := RunServerWithConfig(lconf)
|
||||
defer ln.Shutdown()
|
||||
|
||||
checkLeafNodeConnected(t, s)
|
||||
|
||||
lgacc, _ := ln.LookupAccount("$G")
|
||||
|
||||
rc := createRouteConn(t, opts.Cluster.Host, opts.Cluster.Port)
|
||||
defer rc.Close()
|
||||
|
||||
routeID := "LNOC:22"
|
||||
routeSend, routeExpect := setupRouteEx(t, rc, opts, routeID)
|
||||
|
||||
pingPong := func() {
|
||||
t.Helper()
|
||||
routeSend("PING\r\n")
|
||||
routeExpect(pongRe)
|
||||
}
|
||||
|
||||
info := checkInfoMsg(t, rc)
|
||||
info.ID = routeID
|
||||
info.LNOC = true
|
||||
b, err := json.Marshal(info)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not marshal test route info: %v", err)
|
||||
}
|
||||
|
||||
routeSend(fmt.Sprintf("INFO %s\r\n", b))
|
||||
routeExpect(rsubRe)
|
||||
pingPong()
|
||||
|
||||
// Make sure it can process and LS+
|
||||
routeSend("LS+ ln1 $G foo\r\n")
|
||||
pingPong()
|
||||
|
||||
if !gacc.SubscriptionInterest("foo") {
|
||||
t.Fatalf("Expected interest on \"foo\"")
|
||||
}
|
||||
|
||||
// This should not have been sent to the leafnode since same origin cluster.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
if lgacc.SubscriptionInterest("foo") {
|
||||
t.Fatalf("Did not expect interest on \"foo\"")
|
||||
}
|
||||
|
||||
// Create a connection on the leafnode server.
|
||||
nc, err := nats.Connect(ln.ClientURL())
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error connecting %v", err)
|
||||
}
|
||||
defer nc.Close()
|
||||
|
||||
sub, _ := nc.SubscribeSync("bar")
|
||||
// Let it propagate to the main server
|
||||
checkFor(t, time.Second, 10*time.Millisecond, func() error {
|
||||
if !gacc.SubscriptionInterest("bar") {
|
||||
return fmt.Errorf("No interest")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
// For "bar"
|
||||
routeExpect(rlsubRe)
|
||||
|
||||
// Now pretend like we send a message to the main server over the
|
||||
// route but from the same origin cluster, should not be delivered
|
||||
// to the leafnode.
|
||||
|
||||
// Make sure it can process and LMSG.
|
||||
// LMSG for routes is like HMSG with an origin cluster before the account.
|
||||
routeSend("LMSG ln1 $G bar 0 2\r\nok\r\n")
|
||||
pingPong()
|
||||
|
||||
// Let it propagate if not properly truncated.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
if n, _, _ := sub.Pending(); n != 0 {
|
||||
t.Fatalf("Should not have received the message on bar")
|
||||
}
|
||||
|
||||
// Try one with all the bells and whistles.
|
||||
routeSend("LMSG ln1 $G foo + reply bar baz 0 2\r\nok\r\n")
|
||||
pingPong()
|
||||
|
||||
// Let it propagate if not properly truncated.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
if n, _, _ := sub.Pending(); n != 0 {
|
||||
t.Fatalf("Should not have received the message on bar")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,6 +315,7 @@ var (
|
||||
lsubRe = regexp.MustCompile(`LS\+\s+([^\s]+)\s*([^\s]+)?\s*(\d+)?\r\n`)
|
||||
lunsubRe = regexp.MustCompile(`LS\-\s+([^\s]+)\s*([^\s]+)?\r\n`)
|
||||
lmsgRe = regexp.MustCompile(`(?:(?:LMSG\s+([^\s]+)\s+(?:([|+]\s+([\w\s]+)|[^\s]+)[^\S\r\n]+)?(\d+)\s*\r\n([^\\r\\n]*?)\r\n)+?)`)
|
||||
rlsubRe = regexp.MustCompile(`LS\+\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*([^\s]+)?\s*(\d+)?\r\n`)
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Reference in New Issue
Block a user