get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/test/system_services_test.go (about)

     1  // Copyright 2019 The NATS Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package test
    15  
    16  import (
    17  	"fmt"
    18  	"math/rand"
    19  	"net/url"
    20  	"strconv"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  
    25  	"get.pme.sh/pnats/server"
    26  	"github.com/nats-io/nats.go"
    27  )
    28  
    29  const dbgSubs = "$SYS.DEBUG.SUBSCRIBERS"
    30  
    31  func (sc *supercluster) selectRandomServer() *server.Options {
    32  	ci := rand.Int31n(int32(len(sc.clusters)))
    33  	si := rand.Int31n(int32(len(sc.clusters[ci].servers)))
    34  	return sc.clusters[ci].opts[si]
    35  }
    36  
    37  func (sc *supercluster) setupSystemServicesImports(t *testing.T, account string) {
    38  	t.Helper()
    39  	for _, c := range sc.clusters {
    40  		for _, s := range c.servers {
    41  			sysAcc := s.SystemAccount()
    42  			if sysAcc == nil {
    43  				t.Fatalf("System account not set")
    44  			}
    45  			acc, err := s.LookupAccount(account)
    46  			if err != nil {
    47  				t.Fatalf("Error looking up account '%s': %v", account, err)
    48  			}
    49  			if err := acc.AddServiceImport(sysAcc, dbgSubs, dbgSubs); err != nil {
    50  				t.Fatalf("Error adding subscribers debug service to '%s': %v", account, err)
    51  			}
    52  		}
    53  	}
    54  }
    55  
    56  func numSubs(t *testing.T, msg *nats.Msg) int {
    57  	t.Helper()
    58  	if msg == nil || msg.Data == nil {
    59  		t.Fatalf("No response")
    60  	}
    61  	n, err := strconv.Atoi(string(msg.Data))
    62  	if err != nil {
    63  		t.Fatalf("Got non-number response: %v", err)
    64  	}
    65  	return n
    66  }
    67  
    68  func checkDbgNumSubs(t *testing.T, nc *nats.Conn, subj string, expected int) {
    69  	t.Helper()
    70  	var n int
    71  	for i := 0; i < 3; i++ {
    72  		response, err := nc.Request(dbgSubs, []byte(subj), 250*time.Millisecond)
    73  		if err != nil {
    74  			continue
    75  		}
    76  		if n = numSubs(t, response); n == expected {
    77  			return
    78  		}
    79  	}
    80  	t.Fatalf("Expected %d subscribers, got %d", expected, n)
    81  }
    82  
    83  func TestSystemServiceSubscribers(t *testing.T) {
    84  	numServers, numClusters := 3, 3
    85  	sc := createSuperCluster(t, numServers, numClusters)
    86  	defer sc.shutdown()
    87  
    88  	sc.setupSystemServicesImports(t, "FOO")
    89  
    90  	nc := clientConnect(t, sc.clusters[0].opts[0], "foo")
    91  	defer nc.Close()
    92  
    93  	checkInterest := func(expected int) {
    94  		t.Helper()
    95  		checkDbgNumSubs(t, nc, "foo.bar", expected)
    96  	}
    97  
    98  	checkInterest(0)
    99  
   100  	// Now add in local subscribers.
   101  	for i := 0; i < 5; i++ {
   102  		nc := clientConnect(t, sc.clusters[0].opts[0], "foo")
   103  		defer nc.Close()
   104  		nc.SubscribeSync("foo.bar")
   105  		nc.SubscribeSync("foo.*")
   106  		nc.Flush()
   107  	}
   108  
   109  	checkInterest(10)
   110  
   111  	// Now create remote subscribers at random.
   112  	for i := 0; i < 90; i++ {
   113  		nc := clientConnect(t, sc.selectRandomServer(), "foo")
   114  		defer nc.Close()
   115  		nc.SubscribeSync("foo.bar")
   116  		nc.Flush()
   117  	}
   118  
   119  	checkInterest(100)
   120  }
   121  
   122  // Test that we can match wildcards. So sub may be foo.bar and we ask about foo.*, that should work.
   123  func TestSystemServiceSubscribersWildcards(t *testing.T) {
   124  	numServers, numClusters := 3, 3
   125  	sc := createSuperCluster(t, numServers, numClusters)
   126  	defer sc.shutdown()
   127  
   128  	sc.setupSystemServicesImports(t, "FOO")
   129  
   130  	nc := clientConnect(t, sc.clusters[0].opts[0], "foo")
   131  	defer nc.Close()
   132  
   133  	for i := 0; i < 50; i++ {
   134  		nc := clientConnect(t, sc.selectRandomServer(), "foo")
   135  		defer nc.Close()
   136  		nc.SubscribeSync(fmt.Sprintf("foo.bar.%d", i+1))
   137  		nc.SubscribeSync(fmt.Sprintf("%d", i+1))
   138  		nc.Flush()
   139  	}
   140  
   141  	checkDbgNumSubs(t, nc, "foo.bar.*", 50)
   142  
   143  	checkDbgNumSubs(t, nc, "foo.>", 50)
   144  
   145  	checkDbgNumSubs(t, nc, "foo.bar.22", 1)
   146  
   147  	response, _ := nc.Request(dbgSubs, []byte("_INBOX.*.*"), time.Second)
   148  	hasInbox := numSubs(t, response)
   149  
   150  	checkDbgNumSubs(t, nc, ">", 100+hasInbox)
   151  }
   152  
   153  // Test that we can match on queue groups as well. Separate request payload with any whitespace.
   154  func TestSystemServiceSubscribersQueueGroups(t *testing.T) {
   155  	numServers, numClusters := 3, 3
   156  	sc := createSuperCluster(t, numServers, numClusters)
   157  	defer sc.shutdown()
   158  
   159  	sc.setupSystemServicesImports(t, "FOO")
   160  
   161  	nc := clientConnect(t, sc.clusters[0].opts[0], "foo")
   162  	defer nc.Close()
   163  
   164  	for i := 0; i < 10; i++ {
   165  		nc := clientConnect(t, sc.selectRandomServer(), "foo")
   166  		defer nc.Close()
   167  		subj := fmt.Sprintf("foo.bar.%d", i+1)
   168  		nc.QueueSubscribeSync(subj, "QG.11")
   169  		nc.QueueSubscribeSync("foo.baz", "QG.33")
   170  		nc.Flush()
   171  	}
   172  
   173  	for i := 0; i < 23; i++ {
   174  		nc := clientConnect(t, sc.selectRandomServer(), "foo")
   175  		defer nc.Close()
   176  		subj := fmt.Sprintf("foo.bar.%d", i+1)
   177  		nc.QueueSubscribeSync(subj, "QG.22")
   178  		nc.QueueSubscribeSync("foo.baz", "QG.22")
   179  		nc.Flush()
   180  	}
   181  
   182  	checkDbgNumSubs(t, nc, "foo.bar.*", 33)
   183  
   184  	checkDbgNumSubs(t, nc, "foo.bar.22 QG.22", 1)
   185  
   186  	checkDbgNumSubs(t, nc, "foo.bar.2", 2)
   187  
   188  	checkDbgNumSubs(t, nc, "foo.baz", 33)
   189  
   190  	checkDbgNumSubs(t, nc, "foo.baz QG.22", 23)
   191  
   192  	// Now check qfilters work on wildcards too.
   193  	checkDbgNumSubs(t, nc, "foo.bar.> QG.11", 10)
   194  
   195  	checkDbgNumSubs(t, nc, "*.baz QG.22", 23)
   196  
   197  	checkDbgNumSubs(t, nc, "foo.*.22 QG.22", 1)
   198  }
   199  
   200  func TestSystemServiceSubscribersLeafNodesWithoutSystem(t *testing.T) {
   201  	numServers, numClusters := 3, 3
   202  	sc := createSuperCluster(t, numServers, numClusters)
   203  	defer sc.shutdown()
   204  
   205  	sc.setupSystemServicesImports(t, "FOO")
   206  
   207  	ci := rand.Int31n(int32(len(sc.clusters)))
   208  	si := rand.Int31n(int32(len(sc.clusters[ci].servers)))
   209  	s, opts := sc.clusters[ci].servers[si], sc.clusters[ci].opts[si]
   210  	url := fmt.Sprintf("nats://%s:pass@%s:%d", "foo", opts.Host, opts.LeafNode.Port)
   211  	ls, lopts := runSolicitLeafServerToURL(url)
   212  	defer ls.Shutdown()
   213  
   214  	checkLeafNodeConnected(t, s)
   215  
   216  	// This is so we can test when the subs on a leafnode are flushed to the connected supercluster.
   217  	fsubj := "__leaf.flush__"
   218  	fc := clientConnect(t, opts, "foo")
   219  	defer fc.Close()
   220  	fc.Subscribe(fsubj, func(m *nats.Msg) {
   221  		m.Respond(nil)
   222  	})
   223  
   224  	lnc := clientConnect(t, lopts, "$G")
   225  	defer lnc.Close()
   226  
   227  	flushLeaf := func() {
   228  		if _, err := lnc.Request(fsubj, nil, time.Second); err != nil {
   229  			t.Fatalf("Did not flush through to the supercluster: %v", err)
   230  		}
   231  	}
   232  
   233  	for i := 0; i < 10; i++ {
   234  		nc := clientConnect(t, sc.selectRandomServer(), "foo")
   235  		defer nc.Close()
   236  		nc.SubscribeSync(fmt.Sprintf("foo.bar.%d", i+1))
   237  		nc.QueueSubscribeSync("foo.bar.baz", "QG.22")
   238  		nc.Flush()
   239  	}
   240  
   241  	nc := clientConnect(t, sc.clusters[0].opts[0], "foo")
   242  	defer nc.Close()
   243  
   244  	checkDbgNumSubs(t, nc, "foo.bar.*", 20)
   245  
   246  	checkDbgNumSubs(t, nc, "foo.bar.3", 1)
   247  
   248  	lnc.SubscribeSync("foo.bar.3")
   249  	lnc.QueueSubscribeSync("foo.bar.baz", "QG.22")
   250  
   251  	// We could flush here but that does not guarantee we have flushed through to the supercluster.
   252  	flushLeaf()
   253  
   254  	checkDbgNumSubs(t, nc, "foo.bar.3", 2)
   255  
   256  	checkDbgNumSubs(t, nc, "foo.bar.baz QG.22", 11)
   257  
   258  	lnc.SubscribeSync("foo.bar.3")
   259  	lnc.QueueSubscribeSync("foo.bar.baz", "QG.22")
   260  	flushLeaf()
   261  
   262  	// For now we do not see all the details behind a leafnode if the leafnode is not enabled.
   263  	checkDbgNumSubs(t, nc, "foo.bar.3", 2)
   264  
   265  	checkDbgNumSubs(t, nc, "foo.bar.baz QG.22", 12)
   266  }
   267  
   268  func runSolicitLeafServerWithSystemToURL(surl string) (*server.Server, *server.Options) {
   269  	o := DefaultTestOptions
   270  	o.Port = -1
   271  	fooAcc := server.NewAccount("FOO")
   272  	o.Accounts = []*server.Account{server.NewAccount("$SYS"), fooAcc}
   273  	o.SystemAccount = "$SYS"
   274  	o.Users = []*server.User{
   275  		{Username: "foo", Password: "pass", Permissions: nil, Account: fooAcc},
   276  	}
   277  	rurl, _ := url.Parse(surl)
   278  	sysUrl, _ := url.Parse(strings.Replace(surl, rurl.User.Username(), "sys", -1))
   279  	o.LeafNode.Remotes = []*server.RemoteLeafOpts{
   280  		{
   281  			URLs:         []*url.URL{rurl},
   282  			LocalAccount: "FOO",
   283  		},
   284  		{
   285  			URLs:         []*url.URL{sysUrl},
   286  			LocalAccount: "$SYS",
   287  		},
   288  	}
   289  	o.LeafNode.ReconnectInterval = 100 * time.Millisecond
   290  	return RunServer(&o), &o
   291  }
   292  
   293  func TestSystemServiceSubscribersLeafNodesWithSystem(t *testing.T) {
   294  	numServers, numClusters := 3, 3
   295  	sc := createSuperCluster(t, numServers, numClusters)
   296  	defer sc.shutdown()
   297  
   298  	sc.setupSystemServicesImports(t, "FOO")
   299  
   300  	ci := rand.Int31n(int32(len(sc.clusters)))
   301  	si := rand.Int31n(int32(len(sc.clusters[ci].servers)))
   302  	s, opts := sc.clusters[ci].servers[si], sc.clusters[ci].opts[si]
   303  	url := fmt.Sprintf("nats://%s:pass@%s:%d", "foo", opts.Host, opts.LeafNode.Port)
   304  	ls, lopts := runSolicitLeafServerWithSystemToURL(url)
   305  	defer ls.Shutdown()
   306  
   307  	checkFor(t, 5*time.Second, 100*time.Millisecond, func() error {
   308  		if nln := s.NumLeafNodes(); nln != 2 {
   309  			return fmt.Errorf("Expected a connected leafnode for server %q, got none", s.ID())
   310  		}
   311  		return nil
   312  	})
   313  
   314  	// This is so we can test when the subs on a leafnode are flushed to the connected supercluster.
   315  	fsubj := "__leaf.flush__"
   316  	fc := clientConnect(t, opts, "foo")
   317  	defer fc.Close()
   318  	fc.Subscribe(fsubj, func(m *nats.Msg) {
   319  		m.Respond(nil)
   320  	})
   321  
   322  	lnc := clientConnect(t, lopts, "foo")
   323  	defer lnc.Close()
   324  
   325  	flushLeaf := func() {
   326  		if _, err := lnc.Request(fsubj, nil, time.Second); err != nil {
   327  			t.Fatalf("Did not flush through to the supercluster: %v", err)
   328  		}
   329  	}
   330  
   331  	for i := 0; i < 10; i++ {
   332  		nc := clientConnect(t, sc.selectRandomServer(), "foo")
   333  		defer nc.Close()
   334  		nc.SubscribeSync(fmt.Sprintf("foo.bar.%d", i+1))
   335  		nc.QueueSubscribeSync("foo.bar.baz", "QG.22")
   336  		nc.Flush()
   337  	}
   338  
   339  	nc := clientConnect(t, sc.clusters[0].opts[0], "foo")
   340  	defer nc.Close()
   341  
   342  	checkDbgNumSubs(t, nc, "foo.bar.3", 1)
   343  
   344  	checkDbgNumSubs(t, nc, "foo.bar.*", 20)
   345  
   346  	lnc.SubscribeSync("foo.bar.3")
   347  	lnc.QueueSubscribeSync("foo.bar.baz", "QG.22")
   348  	flushLeaf()
   349  
   350  	// Since we are doing real tracking now on the other side, this will be off by 1 since we are counting
   351  	// the leaf and the real sub.
   352  	checkDbgNumSubs(t, nc, "foo.bar.3", 3)
   353  
   354  	checkDbgNumSubs(t, nc, "foo.bar.baz QG.22", 12)
   355  }