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:
Derek Collison
2020-06-26 08:15:27 -07:00
parent 1a590eea78
commit 6c805eebc7
12 changed files with 763 additions and 51 deletions

View File

@@ -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"))
}
}

View File

@@ -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")
}
}

View File

@@ -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 (