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

     1  // Copyright 2020-2022 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  //go:build !skip_js_tests
    15  // +build !skip_js_tests
    16  
    17  package server
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	jwt "github.com/nats-io/jwt/v2"
    27  	"github.com/nats-io/nats.go"
    28  	"github.com/nats-io/nkeys"
    29  )
    30  
    31  func TestJetStreamLeafNodeUniqueServerNameCrossJSDomain(t *testing.T) {
    32  	name := "NOT-UNIQUE"
    33  	test := func(s *Server, sIdExpected string, srvs ...*Server) {
    34  		ids := map[string]string{}
    35  		for _, srv := range srvs {
    36  			checkLeafNodeConnectedCount(t, srv, 2)
    37  			ids[srv.ID()] = srv.opts.JetStreamDomain
    38  		}
    39  		// ensure that an update for every server was received
    40  		sysNc := natsConnect(t, fmt.Sprintf("nats://admin:s3cr3t!@127.0.0.1:%d", s.opts.Port))
    41  		defer sysNc.Close()
    42  		sub, err := sysNc.SubscribeSync(fmt.Sprintf(serverStatsSubj, "*"))
    43  		require_NoError(t, err)
    44  		for {
    45  			m, err := sub.NextMsg(time.Second)
    46  			require_NoError(t, err)
    47  			tk := strings.Split(m.Subject, ".")
    48  			if domain, ok := ids[tk[2]]; ok {
    49  				delete(ids, tk[2])
    50  				require_Contains(t, string(m.Data), fmt.Sprintf(`"domain":"%s"`, domain))
    51  			}
    52  			if len(ids) == 0 {
    53  				break
    54  			}
    55  		}
    56  		cnt := 0
    57  		s.nodeToInfo.Range(func(key, value interface{}) bool {
    58  			cnt++
    59  			require_Equal(t, value.(nodeInfo).name, name)
    60  			require_Equal(t, value.(nodeInfo).id, sIdExpected)
    61  			return true
    62  		})
    63  		require_True(t, cnt == 1)
    64  	}
    65  	tmplA := `
    66  		listen: -1
    67  		server_name: %s
    68  		jetstream {
    69  			max_mem_store: 256MB,
    70  			max_file_store: 2GB,
    71  			store_dir: '%s',
    72  			domain: hub
    73  		}
    74  		accounts {
    75  			JSY { users = [ { user: "y", pass: "p" } ]; jetstream: true }
    76  			$SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }
    77  		}
    78  		leaf {
    79  			port: -1
    80  		}
    81      `
    82  	tmplL := `
    83  		listen: -1
    84  		server_name: %s
    85  		jetstream {
    86  			max_mem_store: 256MB,
    87  			max_file_store: 2GB,
    88  			store_dir: '%s',
    89  			domain: %s
    90  		}
    91  		accounts {
    92  			JSY { users = [ { user: "y", pass: "p" } ]; jetstream: true }
    93  			$SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] }
    94  		}
    95  		leaf {
    96  			remotes [
    97  				{ urls: [ %s ], account: "JSY" }
    98  				{ urls: [ %s ], account: "$SYS" }
    99  			]
   100  		}
   101      `
   102  	t.Run("same-domain", func(t *testing.T) {
   103  		confA := createConfFile(t, []byte(fmt.Sprintf(tmplA, name, t.TempDir())))
   104  		sA, oA := RunServerWithConfig(confA)
   105  		defer sA.Shutdown()
   106  		// using same domain as sA
   107  		confL := createConfFile(t, []byte(fmt.Sprintf(tmplL, name, t.TempDir(), "hub",
   108  			fmt.Sprintf("nats://y:p@127.0.0.1:%d", oA.LeafNode.Port),
   109  			fmt.Sprintf("nats://admin:s3cr3t!@127.0.0.1:%d", oA.LeafNode.Port))))
   110  		sL, _ := RunServerWithConfig(confL)
   111  		defer sL.Shutdown()
   112  		// as server name uniqueness is violates, sL.ID() is the expected value
   113  		test(sA, sL.ID(), sA, sL)
   114  	})
   115  	t.Run("different-domain", func(t *testing.T) {
   116  		confA := createConfFile(t, []byte(fmt.Sprintf(tmplA, name, t.TempDir())))
   117  		sA, oA := RunServerWithConfig(confA)
   118  		defer sA.Shutdown()
   119  		// using different domain as sA
   120  		confL := createConfFile(t, []byte(fmt.Sprintf(tmplL, name, t.TempDir(), "spoke",
   121  			fmt.Sprintf("nats://y:p@127.0.0.1:%d", oA.LeafNode.Port),
   122  			fmt.Sprintf("nats://admin:s3cr3t!@127.0.0.1:%d", oA.LeafNode.Port))))
   123  		sL, _ := RunServerWithConfig(confL)
   124  		defer sL.Shutdown()
   125  		checkLeafNodeConnectedCount(t, sL, 2)
   126  		checkLeafNodeConnectedCount(t, sA, 2)
   127  		// ensure sA contains only sA.ID
   128  		test(sA, sA.ID(), sA, sL)
   129  	})
   130  }
   131  
   132  func TestJetStreamLeafNodeJwtPermsAndJSDomains(t *testing.T) {
   133  	createAcc := func(js bool) (string, string, nkeys.KeyPair) {
   134  		kp, _ := nkeys.CreateAccount()
   135  		aPub, _ := kp.PublicKey()
   136  		claim := jwt.NewAccountClaims(aPub)
   137  		if js {
   138  			claim.Limits.JetStreamLimits = jwt.JetStreamLimits{
   139  				MemoryStorage: 1024 * 1024,
   140  				DiskStorage:   1024 * 1024,
   141  				Streams:       1, Consumer: 2}
   142  		}
   143  		aJwt, err := claim.Encode(oKp)
   144  		require_NoError(t, err)
   145  		return aPub, aJwt, kp
   146  	}
   147  	sysPub, sysJwt, sysKp := createAcc(false)
   148  	accPub, accJwt, accKp := createAcc(true)
   149  	noExpiration := time.Now().Add(time.Hour)
   150  	// create user for acc to be used in leaf node.
   151  	lnCreds := createUserWithLimit(t, accKp, noExpiration, func(j *jwt.UserPermissionLimits) {
   152  		j.Sub.Deny.Add("subdeny")
   153  		j.Pub.Deny.Add("pubdeny")
   154  	})
   155  	unlimitedCreds := createUserWithLimit(t, accKp, noExpiration, nil)
   156  
   157  	sysCreds := createUserWithLimit(t, sysKp, noExpiration, nil)
   158  
   159  	tmplA := `
   160  operator: %s
   161  system_account: %s
   162  resolver: MEMORY
   163  resolver_preload: {
   164    %s: %s
   165    %s: %s
   166  }
   167  listen: 127.0.0.1:-1
   168  leafnodes: {
   169  	listen: 127.0.0.1:-1
   170  }
   171  jetstream :{
   172      domain: "cluster"
   173      store_dir: '%s'
   174      max_mem: 100Mb
   175      max_file: 100Mb
   176  }
   177  `
   178  
   179  	tmplL := `
   180  listen: 127.0.0.1:-1
   181  accounts :{
   182      A:{   jetstream: enable, users:[ {user:a1,password:a1}]},
   183      SYS:{ users:[ {user:s1,password:s1}]},
   184  }
   185  system_account = SYS
   186  jetstream: {
   187      domain: ln1
   188      store_dir: '%s'
   189      max_mem: 50Mb
   190      max_file: 50Mb
   191  }
   192  leafnodes:{
   193      remotes:[{ url:nats://127.0.0.1:%d, account: A, credentials: '%s'},
   194  			 { url:nats://127.0.0.1:%d, account: SYS, credentials: '%s'}]
   195  }
   196  `
   197  
   198  	confA := createConfFile(t, []byte(fmt.Sprintf(tmplA, ojwt, sysPub,
   199  		sysPub, sysJwt, accPub, accJwt,
   200  		t.TempDir())))
   201  	sA, _ := RunServerWithConfig(confA)
   202  	defer sA.Shutdown()
   203  
   204  	confL := createConfFile(t, []byte(fmt.Sprintf(tmplL, t.TempDir(),
   205  		sA.opts.LeafNode.Port, lnCreds, sA.opts.LeafNode.Port, sysCreds)))
   206  	sL, _ := RunServerWithConfig(confL)
   207  	defer sL.Shutdown()
   208  
   209  	checkLeafNodeConnectedCount(t, sA, 2)
   210  	checkLeafNodeConnectedCount(t, sL, 2)
   211  
   212  	ncA := natsConnect(t, sA.ClientURL(), nats.UserCredentials(unlimitedCreds))
   213  	defer ncA.Close()
   214  
   215  	ncL := natsConnect(t, fmt.Sprintf("nats://a1:a1@127.0.0.1:%d", sL.opts.Port))
   216  	defer ncL.Close()
   217  
   218  	test := func(subject string, cSub, cPub *nats.Conn, remoteServerForSub *Server, accName string, pass bool) {
   219  		t.Helper()
   220  		sub, err := cSub.SubscribeSync(subject)
   221  		require_NoError(t, err)
   222  		require_NoError(t, cSub.Flush())
   223  		// ensure the subscription made it across, or if not sent due to sub deny, make sure it could have made it.
   224  		if remoteServerForSub == nil {
   225  			time.Sleep(200 * time.Millisecond)
   226  		} else {
   227  			checkSubInterest(t, remoteServerForSub, accName, subject, time.Second)
   228  		}
   229  		require_NoError(t, cPub.Publish(subject, []byte("hello world")))
   230  		require_NoError(t, cPub.Flush())
   231  		m, err := sub.NextMsg(500 * time.Millisecond)
   232  		if pass {
   233  			require_NoError(t, err)
   234  			require_True(t, m.Subject == subject)
   235  			require_Equal(t, string(m.Data), "hello world")
   236  		} else {
   237  			require_True(t, err == nats.ErrTimeout)
   238  		}
   239  	}
   240  
   241  	t.Run("sub-on-ln-pass", func(t *testing.T) {
   242  		test("sub", ncL, ncA, sA, accPub, true)
   243  	})
   244  	t.Run("sub-on-ln-fail", func(t *testing.T) {
   245  		test("subdeny", ncL, ncA, nil, "", false)
   246  	})
   247  	t.Run("pub-on-ln-pass", func(t *testing.T) {
   248  		test("pub", ncA, ncL, sL, "A", true)
   249  	})
   250  	t.Run("pub-on-ln-fail", func(t *testing.T) {
   251  		test("pubdeny", ncA, ncL, nil, "A", false)
   252  	})
   253  }
   254  
   255  func TestJetStreamLeafNodeClusterExtensionWithSystemAccount(t *testing.T) {
   256  	/*
   257  		Topologies tested here
   258  		same == true
   259  		A  <-> B
   260  		^ |\
   261  		|   \
   262  		|  proxy
   263  		|     \
   264  		LA <-> LB
   265  
   266  		same == false
   267  		A  <-> B
   268  		^      ^
   269  		|      |
   270  		|    proxy
   271  		|      |
   272  		LA <-> LB
   273  
   274  		The proxy is turned on later, such that the system account connection can be started later, in a controlled way
   275  		This explicitly tests the system state before and after this happens.
   276  	*/
   277  
   278  	tmplA := `
   279  listen: 127.0.0.1:-1
   280  accounts :{
   281      A:{   jetstream: enable, users:[ {user:a1,password:a1}]},
   282      SYS:{ users:[ {user:s1,password:s1}]},
   283  }
   284  system_account: SYS
   285  leafnodes: {
   286  	listen: 127.0.0.1:-1
   287  	no_advertise: true
   288  	authorization: {
   289  		timeout: 0.5
   290  	}
   291  }
   292  jetstream :{
   293      domain: "cluster"
   294      store_dir: '%s'
   295      max_mem: 100Mb
   296      max_file: 100Mb
   297  }
   298  server_name: A
   299  cluster: {
   300  	name: clust1
   301  	listen: 127.0.0.1:20104
   302  	routes=[nats-route://127.0.0.1:20105]
   303  	no_advertise: true
   304  }
   305  `
   306  
   307  	tmplB := `
   308  listen: 127.0.0.1:-1
   309  accounts :{
   310      A:{   jetstream: enable, users:[ {user:a1,password:a1}]},
   311      SYS:{ users:[ {user:s1,password:s1}]},
   312  }
   313  system_account: SYS
   314  leafnodes: {
   315  	listen: 127.0.0.1:-1
   316  	no_advertise: true
   317  	authorization: {
   318  		timeout: 0.5
   319  	}
   320  }
   321  jetstream: {
   322      domain: "cluster"
   323      store_dir: '%s'
   324      max_mem: 100Mb
   325      max_file: 100Mb
   326  }
   327  server_name: B
   328  cluster: {
   329  	name: clust1
   330  	listen: 127.0.0.1:20105
   331  	routes=[nats-route://127.0.0.1:20104]
   332  	no_advertise: true
   333  }
   334  `
   335  
   336  	tmplLA := `
   337  listen: 127.0.0.1:-1
   338  accounts :{
   339      A:{   jetstream: enable, users:[ {user:a1,password:a1}]},
   340      SYS:{ users:[ {user:s1,password:s1}]},
   341  }
   342  system_account = SYS
   343  jetstream: {
   344      domain: "cluster"
   345      store_dir: '%s'
   346      max_mem: 50Mb
   347      max_file: 50Mb
   348  	%s
   349  }
   350  server_name: LA
   351  cluster: {
   352  	name: clustL
   353  	listen: 127.0.0.1:20106
   354  	routes=[nats-route://127.0.0.1:20107]
   355  	no_advertise: true
   356  }
   357  leafnodes:{
   358  	no_advertise: true
   359      remotes:[{url:nats://a1:a1@127.0.0.1:%d, account: A},
   360  		     {url:nats://s1:s1@127.0.0.1:%d, account: SYS}]
   361  }
   362  `
   363  
   364  	tmplLB := `
   365  listen: 127.0.0.1:-1
   366  accounts :{
   367      A:{   jetstream: enable, users:[ {user:a1,password:a1}]},
   368      SYS:{ users:[ {user:s1,password:s1}]},
   369  }
   370  system_account = SYS
   371  jetstream: {
   372      domain: "cluster"
   373      store_dir: '%s'
   374      max_mem: 50Mb
   375      max_file: 50Mb
   376  	%s
   377  }
   378  server_name: LB
   379  cluster: {
   380  	name: clustL
   381  	listen: 127.0.0.1:20107
   382  	routes=[nats-route://127.0.0.1:20106]
   383  	no_advertise: true
   384  }
   385  leafnodes:{
   386  	no_advertise: true
   387      remotes:[{url:nats://a1:a1@127.0.0.1:%d, account: A},
   388  		     {url:nats://s1:s1@127.0.0.1:%d, account: SYS}]
   389  }
   390  `
   391  
   392  	for _, testCase := range []struct {
   393  		// which topology to pick
   394  		same bool
   395  		// If leaf server should be operational and form a Js cluster prior to joining.
   396  		// In this setup this would be an error as you give the wrong hint.
   397  		// But this should work itself out regardless
   398  		leafFunctionPreJoin bool
   399  	}{
   400  		{true, true},
   401  		{true, false},
   402  		{false, true},
   403  		{false, false}} {
   404  		t.Run(fmt.Sprintf("%t-%t", testCase.same, testCase.leafFunctionPreJoin), func(t *testing.T) {
   405  			sd1 := t.TempDir()
   406  			confA := createConfFile(t, []byte(fmt.Sprintf(tmplA, sd1)))
   407  			sA, _ := RunServerWithConfig(confA)
   408  			defer sA.Shutdown()
   409  
   410  			sd2 := t.TempDir()
   411  			confB := createConfFile(t, []byte(fmt.Sprintf(tmplB, sd2)))
   412  			sB, _ := RunServerWithConfig(confB)
   413  			defer sB.Shutdown()
   414  
   415  			checkClusterFormed(t, sA, sB)
   416  
   417  			c := cluster{t: t, servers: []*Server{sA, sB}}
   418  			c.waitOnLeader()
   419  
   420  			// starting this will allow the second remote in tmplL to successfully connect.
   421  			port := sB.opts.LeafNode.Port
   422  			if testCase.same {
   423  				port = sA.opts.LeafNode.Port
   424  			}
   425  			p := &proxyAcceptDetectFailureLate{acceptPort: port}
   426  			defer p.close()
   427  			lPort := p.runEx(t, true)
   428  
   429  			hint := ""
   430  			if testCase.leafFunctionPreJoin {
   431  				hint = fmt.Sprintf("extension_hint: %s", strings.ToUpper(jsNoExtend))
   432  			}
   433  
   434  			sd3 := t.TempDir()
   435  			// deliberately pick server sA and proxy
   436  			confLA := createConfFile(t, []byte(fmt.Sprintf(tmplLA, sd3, hint, sA.opts.LeafNode.Port, lPort)))
   437  			sLA, _ := RunServerWithConfig(confLA)
   438  			defer sLA.Shutdown()
   439  
   440  			sd4 := t.TempDir()
   441  			// deliberately pick server sA and proxy
   442  			confLB := createConfFile(t, []byte(fmt.Sprintf(tmplLB, sd4, hint, sA.opts.LeafNode.Port, lPort)))
   443  			sLB, _ := RunServerWithConfig(confLB)
   444  			defer sLB.Shutdown()
   445  
   446  			checkClusterFormed(t, sLA, sLB)
   447  
   448  			strmCfg := func(name, placementCluster string) *nats.StreamConfig {
   449  				if placementCluster == "" {
   450  					return &nats.StreamConfig{Name: name, Replicas: 1, Subjects: []string{name}}
   451  				}
   452  				return &nats.StreamConfig{Name: name, Replicas: 1, Subjects: []string{name},
   453  					Placement: &nats.Placement{Cluster: placementCluster}}
   454  			}
   455  			// Only after the system account is fully connected can streams be placed anywhere.
   456  			testJSFunctions := func(pass bool) {
   457  				ncA := natsConnect(t, fmt.Sprintf("nats://a1:a1@127.0.0.1:%d", sA.opts.Port))
   458  				defer ncA.Close()
   459  				jsA, err := ncA.JetStream()
   460  				require_NoError(t, err)
   461  				_, err = jsA.AddStream(strmCfg(fmt.Sprintf("fooA1-%t", pass), ""))
   462  				require_NoError(t, err)
   463  				_, err = jsA.AddStream(strmCfg(fmt.Sprintf("fooA2-%t", pass), "clust1"))
   464  				require_NoError(t, err)
   465  				_, err = jsA.AddStream(strmCfg(fmt.Sprintf("fooA3-%t", pass), "clustL"))
   466  				if pass {
   467  					require_NoError(t, err)
   468  				} else {
   469  					require_Error(t, err)
   470  					require_Contains(t, err.Error(), "no suitable peers for placement")
   471  				}
   472  				ncL := natsConnect(t, fmt.Sprintf("nats://a1:a1@127.0.0.1:%d", sLA.opts.Port))
   473  				defer ncL.Close()
   474  				jsL, err := ncL.JetStream()
   475  				require_NoError(t, err)
   476  				_, err = jsL.AddStream(strmCfg(fmt.Sprintf("fooL1-%t", pass), ""))
   477  				require_NoError(t, err)
   478  				_, err = jsL.AddStream(strmCfg(fmt.Sprintf("fooL2-%t", pass), "clustL"))
   479  				require_NoError(t, err)
   480  				_, err = jsL.AddStream(strmCfg(fmt.Sprintf("fooL3-%t", pass), "clust1"))
   481  				if pass {
   482  					require_NoError(t, err)
   483  				} else {
   484  					require_Error(t, err)
   485  					require_Contains(t, err.Error(), "no suitable peers for placement")
   486  				}
   487  			}
   488  			clusterLnCnt := func(expected int) error {
   489  				cnt := 0
   490  				for _, s := range c.servers {
   491  					cnt += s.NumLeafNodes()
   492  				}
   493  				if cnt == expected {
   494  					return nil
   495  				}
   496  				return fmt.Errorf("not enought leaf node connections, got %d needed %d", cnt, expected)
   497  			}
   498  
   499  			// Even though there are two remotes defined in tmplL, only one will be able to connect.
   500  			checkFor(t, 10*time.Second, time.Second/4, func() error { return clusterLnCnt(2) })
   501  			checkLeafNodeConnectedCount(t, sLA, 1)
   502  			checkLeafNodeConnectedCount(t, sLB, 1)
   503  			c.waitOnPeerCount(2)
   504  
   505  			if testCase.leafFunctionPreJoin {
   506  				cl := cluster{t: t, servers: []*Server{sLA, sLB}}
   507  				cl.waitOnLeader()
   508  				cl.waitOnPeerCount(2)
   509  				testJSFunctions(false)
   510  			} else {
   511  				// In cases where the leaf nodes have to wait for the system account to connect,
   512  				// JetStream should not be operational during that time
   513  				ncA := natsConnect(t, fmt.Sprintf("nats://a1:a1@127.0.0.1:%d", sLA.opts.Port))
   514  				defer ncA.Close()
   515  				jsA, err := ncA.JetStream()
   516  				require_NoError(t, err)
   517  				_, err = jsA.AddStream(strmCfg("fail-false", ""))
   518  				require_Error(t, err)
   519  			}
   520  			// Starting the proxy will connect the system accounts.
   521  			// After they are connected the clusters are merged.
   522  			// Once this happened, all streams in test can be placed anywhere in the cluster.
   523  			// Before that only the cluster the client is connected to can be used for placement
   524  			p.start()
   525  
   526  			// Even though there are two remotes defined in tmplL, only one will be able to connect.
   527  			checkFor(t, 10*time.Second, time.Second/4, func() error { return clusterLnCnt(4) })
   528  			checkLeafNodeConnectedCount(t, sLA, 2)
   529  			checkLeafNodeConnectedCount(t, sLB, 2)
   530  
   531  			// The leader will reside in the main cluster only
   532  			c.waitOnPeerCount(4)
   533  			testJSFunctions(true)
   534  		})
   535  	}
   536  }
   537  
   538  func TestJetStreamLeafNodeClusterMixedModeExtensionWithSystemAccount(t *testing.T) {
   539  	/*  Topology used in this test:
   540  	CLUSTER(A <-> B <-> C (NO JS))
   541  	      	            ^
   542  	                    |
   543  	                    LA
   544  	*/
   545  
   546  	// once every server is up, we expect these peers to be part of the JetStream meta cluster
   547  	expectedJetStreamPeers := map[string]struct{}{
   548  		"A":  {},
   549  		"B":  {},
   550  		"LA": {},
   551  	}
   552  
   553  	tmplA := `
   554  listen: 127.0.0.1:-1
   555  accounts :{
   556      A:{   jetstream: enable, users:[ {user:a1,password:a1}]},
   557      SYS:{ users:[ {user:s1,password:s1}]},
   558  }
   559  system_account: SYS
   560  leafnodes: {
   561  	listen: 127.0.0.1:-1
   562  	no_advertise: true
   563  	authorization: {
   564  		timeout: 0.5
   565  	}
   566  }
   567  jetstream: { %s store_dir: '%s'; max_mem: 50Mb, max_file: 50Mb }
   568  server_name: A
   569  cluster: {
   570  	name: clust1
   571  	listen: 127.0.0.1:20114
   572  	routes=[nats-route://127.0.0.1:20115,nats-route://127.0.0.1:20116]
   573  	no_advertise: true
   574  }
   575  `
   576  
   577  	tmplB := `
   578  listen: 127.0.0.1:-1
   579  accounts :{
   580      A:{   jetstream: enable, users:[ {user:a1,password:a1}]},
   581      SYS:{ users:[ {user:s1,password:s1}]},
   582  }
   583  system_account: SYS
   584  leafnodes: {
   585  	listen: 127.0.0.1:-1
   586  	no_advertise: true
   587  	authorization: {
   588  		timeout: 0.5
   589  	}
   590  }
   591  jetstream: { %s store_dir: '%s'; max_mem: 50Mb, max_file: 50Mb }
   592  server_name: B
   593  cluster: {
   594  	name: clust1
   595  	listen: 127.0.0.1:20115
   596  	routes=[nats-route://127.0.0.1:20114,nats-route://127.0.0.1:20116]
   597  	no_advertise: true
   598  }
   599  `
   600  
   601  	tmplC := `
   602  listen: 127.0.0.1:-1
   603  accounts :{
   604      A:{   jetstream: enable, users:[ {user:a1,password:a1}]},
   605      SYS:{ users:[ {user:s1,password:s1}]},
   606  }
   607  system_account: SYS
   608  leafnodes: {
   609  	listen: 127.0.0.1:-1
   610  	no_advertise: true
   611  	authorization: {
   612  		timeout: 0.5
   613  	}
   614  }
   615  jetstream: {
   616  	enabled: false
   617  	%s
   618  }
   619  server_name: C
   620  cluster: {
   621  	name: clust1
   622  	listen: 127.0.0.1:20116
   623  	routes=[nats-route://127.0.0.1:20114,nats-route://127.0.0.1:20115]
   624  	no_advertise: true
   625  }
   626  `
   627  
   628  	tmplLA := `
   629  listen: 127.0.0.1:-1
   630  accounts :{
   631      A:{   jetstream: enable, users:[ {user:a1,password:a1}]},
   632      SYS:{ users:[ {user:s1,password:s1}]},
   633  }
   634  system_account = SYS
   635  # the extension hint is to simplify this test. without it present we would need a cluster of size 2
   636  jetstream: { %s store_dir: '%s'; max_mem: 50Mb, max_file: 50Mb, extension_hint: will_extend }
   637  server_name: LA
   638  leafnodes:{
   639  	no_advertise: true
   640      remotes:[{url:nats://a1:a1@127.0.0.1:%d, account: A},
   641  		     {url:nats://s1:s1@127.0.0.1:%d, account: SYS}]
   642  }
   643  # add the cluster here so we can test placement
   644  cluster: { name: clustL }
   645  `
   646  	for _, withDomain := range []bool{true, false} {
   647  		t.Run(fmt.Sprintf("with-domain:%t", withDomain), func(t *testing.T) {
   648  			var jsDisabledDomainString string
   649  			var jsEnabledDomainString string
   650  			if withDomain {
   651  				jsEnabledDomainString = `domain: "domain", `
   652  				jsDisabledDomainString = `domain: "domain"`
   653  			} else {
   654  				// in case no domain name is set, fall back to the extension hint.
   655  				// since JS is disabled, the value of this does not clash with other uses.
   656  				jsDisabledDomainString = "extension_hint: will_extend"
   657  			}
   658  
   659  			sd1 := t.TempDir()
   660  			confA := createConfFile(t, []byte(fmt.Sprintf(tmplA, jsEnabledDomainString, sd1)))
   661  			sA, _ := RunServerWithConfig(confA)
   662  			defer sA.Shutdown()
   663  
   664  			sd2 := t.TempDir()
   665  			confB := createConfFile(t, []byte(fmt.Sprintf(tmplB, jsEnabledDomainString, sd2)))
   666  			sB, _ := RunServerWithConfig(confB)
   667  			defer sB.Shutdown()
   668  
   669  			confC := createConfFile(t, []byte(fmt.Sprintf(tmplC, jsDisabledDomainString)))
   670  			sC, _ := RunServerWithConfig(confC)
   671  			defer sC.Shutdown()
   672  
   673  			checkClusterFormed(t, sA, sB, sC)
   674  			c := cluster{t: t, servers: []*Server{sA, sB, sC}}
   675  			c.waitOnPeerCount(2)
   676  
   677  			sd3 := t.TempDir()
   678  			// deliberately pick server sC (no JS) to connect to
   679  			confLA := createConfFile(t, []byte(fmt.Sprintf(tmplLA, jsEnabledDomainString, sd3, sC.opts.LeafNode.Port, sC.opts.LeafNode.Port)))
   680  			sLA, _ := RunServerWithConfig(confLA)
   681  			defer sLA.Shutdown()
   682  
   683  			checkLeafNodeConnectedCount(t, sC, 2)
   684  			checkLeafNodeConnectedCount(t, sLA, 2)
   685  			c.waitOnPeerCount(3)
   686  			peers := c.leader().JetStreamClusterPeers()
   687  			for _, peer := range peers {
   688  				if _, ok := expectedJetStreamPeers[peer]; !ok {
   689  					t.Fatalf("Found unexpected peer %q", peer)
   690  				}
   691  			}
   692  
   693  			// helper to create stream config with uniqe name and subject
   694  			cnt := 0
   695  			strmCfg := func(placementCluster string) *nats.StreamConfig {
   696  				name := fmt.Sprintf("s-%d", cnt)
   697  				cnt++
   698  				if placementCluster == "" {
   699  					return &nats.StreamConfig{Name: name, Replicas: 1, Subjects: []string{name}}
   700  				}
   701  				return &nats.StreamConfig{Name: name, Replicas: 1, Subjects: []string{name},
   702  					Placement: &nats.Placement{Cluster: placementCluster}}
   703  			}
   704  
   705  			test := func(port int, expectedDefPlacement string) {
   706  				ncA := natsConnect(t, fmt.Sprintf("nats://a1:a1@127.0.0.1:%d", port))
   707  				defer ncA.Close()
   708  				jsA, err := ncA.JetStream()
   709  				require_NoError(t, err)
   710  				si, err := jsA.AddStream(strmCfg(""))
   711  				require_NoError(t, err)
   712  				require_Contains(t, si.Cluster.Name, expectedDefPlacement)
   713  				si, err = jsA.AddStream(strmCfg("clust1"))
   714  				require_NoError(t, err)
   715  				require_Contains(t, si.Cluster.Name, "clust1")
   716  				si, err = jsA.AddStream(strmCfg("clustL"))
   717  				require_NoError(t, err)
   718  				require_Contains(t, si.Cluster.Name, "clustL")
   719  			}
   720  
   721  			test(sA.opts.Port, "clust1")
   722  			test(sB.opts.Port, "clust1")
   723  			test(sC.opts.Port, "clust1")
   724  			test(sLA.opts.Port, "clustL")
   725  		})
   726  	}
   727  }
   728  
   729  func TestJetStreamLeafNodeCredsDenies(t *testing.T) {
   730  	tmplL := `
   731  listen: 127.0.0.1:-1
   732  accounts :{
   733      A:{   jetstream: enable, users:[ {user:a1,password:a1}]},
   734      SYS:{ users:[ {user:s1,password:s1}]},
   735  }
   736  system_account = SYS
   737  jetstream: {
   738      domain: "cluster"
   739      store_dir: '%s'
   740      max_mem: 50Mb
   741      max_file: 50Mb
   742  }
   743  leafnodes:{
   744      remotes:[{url:nats://a1:a1@127.0.0.1:20125, account: A, credentials: '%s' },
   745  		     {url:nats://s1:s1@127.0.0.1:20125, account: SYS, credentials: '%s', deny_imports: foo, deny_exports: bar}]
   746  }
   747  `
   748  	akp, err := nkeys.CreateAccount()
   749  	require_NoError(t, err)
   750  	creds := createUserWithLimit(t, akp, time.Time{}, func(pl *jwt.UserPermissionLimits) {
   751  		pl.Pub.Deny.Add(jsAllAPI)
   752  		pl.Sub.Deny.Add(jsAllAPI)
   753  	})
   754  
   755  	sd := t.TempDir()
   756  
   757  	confL := createConfFile(t, []byte(fmt.Sprintf(tmplL, sd, creds, creds)))
   758  	opts := LoadConfig(confL)
   759  	sL, err := NewServer(opts)
   760  	require_NoError(t, err)
   761  
   762  	l := captureNoticeLogger{}
   763  	sL.SetLogger(&l, false, false)
   764  
   765  	go sL.Start()
   766  	defer sL.Shutdown()
   767  
   768  	// wait till the notices got printed
   769  UNTIL_READY:
   770  	for {
   771  		<-time.After(50 * time.Millisecond)
   772  		l.Lock()
   773  		for _, n := range l.notices {
   774  			if strings.Contains(n, "Server is ready") {
   775  				l.Unlock()
   776  				break UNTIL_READY
   777  			}
   778  		}
   779  		l.Unlock()
   780  	}
   781  
   782  	l.Lock()
   783  	cnt := 0
   784  	for _, n := range l.notices {
   785  		if strings.Contains(n, "LeafNode Remote for Account A uses credentials file") ||
   786  			strings.Contains(n, "LeafNode Remote for System Account uses") ||
   787  			strings.Contains(n, "Remote for System Account uses restricted export permissions") ||
   788  			strings.Contains(n, "Remote for System Account uses restricted import permissions") {
   789  			cnt++
   790  		}
   791  	}
   792  	l.Unlock()
   793  	require_True(t, cnt == 4)
   794  }
   795  
   796  func TestJetStreamLeafNodeDefaultDomainCfg(t *testing.T) {
   797  	tmplHub := `
   798  listen: 127.0.0.1:%d
   799  accounts :{
   800      A:{ jetstream: %s, users:[ {user:a1,password:a1}]},
   801      SYS:{ users:[ {user:s1,password:s1}]},
   802  }
   803  system_account: SYS
   804  jetstream : %s
   805  server_name: HUB
   806  leafnodes: {
   807  	listen: 127.0.0.1:%d
   808  }
   809  %s
   810  `
   811  
   812  	tmplL := `
   813  listen: 127.0.0.1:-1
   814  accounts :{
   815      A:{   jetstream: enable, users:[ {user:a1,password:a1}]},
   816      SYS:{ users:[ {user:s1,password:s1}]},
   817  }
   818  system_account: SYS
   819  jetstream: { domain: "%s", store_dir: '%s', max_mem: 100Mb, max_file: 100Mb }
   820  server_name: LEAF
   821  leafnodes: {
   822      remotes:[{url:nats://a1:a1@127.0.0.1:%d, account: A},%s]
   823  }
   824  %s
   825  `
   826  
   827  	test := func(domain string, sysShared bool) {
   828  		confHub := createConfFile(t, []byte(fmt.Sprintf(tmplHub, -1, "disabled", "disabled", -1, "")))
   829  		sHub, _ := RunServerWithConfig(confHub)
   830  		defer sHub.Shutdown()
   831  
   832  		noDomainFix := ""
   833  		if domain == _EMPTY_ {
   834  			noDomainFix = `default_js_domain:{A:""}`
   835  		}
   836  
   837  		sys := ""
   838  		if sysShared {
   839  			sys = fmt.Sprintf(`{url:nats://s1:s1@127.0.0.1:%d, account: SYS}`, sHub.opts.LeafNode.Port)
   840  		}
   841  
   842  		sdLeaf := t.TempDir()
   843  		confL := createConfFile(t, []byte(fmt.Sprintf(tmplL, domain, sdLeaf, sHub.opts.LeafNode.Port, sys, noDomainFix)))
   844  		sLeaf, _ := RunServerWithConfig(confL)
   845  		defer sLeaf.Shutdown()
   846  
   847  		lnCnt := 1
   848  		if sysShared {
   849  			lnCnt++
   850  		}
   851  
   852  		checkLeafNodeConnectedCount(t, sHub, lnCnt)
   853  		checkLeafNodeConnectedCount(t, sLeaf, lnCnt)
   854  
   855  		ncA := natsConnect(t, fmt.Sprintf("nats://a1:a1@127.0.0.1:%d", sHub.opts.Port))
   856  		defer ncA.Close()
   857  		jsA, err := ncA.JetStream()
   858  		require_NoError(t, err)
   859  
   860  		_, err = jsA.AddStream(&nats.StreamConfig{Name: "foo", Replicas: 1, Subjects: []string{"foo"}})
   861  		require_True(t, err == nats.ErrNoResponders)
   862  
   863  		// Add in default domain and restart server
   864  		require_NoError(t, os.WriteFile(confHub, []byte(fmt.Sprintf(tmplHub,
   865  			sHub.opts.Port,
   866  			"disabled",
   867  			"disabled",
   868  			sHub.opts.LeafNode.Port,
   869  			fmt.Sprintf(`default_js_domain: {A:"%s"}`, domain))), 0664))
   870  
   871  		sHub.Shutdown()
   872  		sHub.WaitForShutdown()
   873  		checkLeafNodeConnectedCount(t, sLeaf, 0)
   874  		sHubUpd1, _ := RunServerWithConfig(confHub)
   875  		defer sHubUpd1.Shutdown()
   876  
   877  		checkLeafNodeConnectedCount(t, sHubUpd1, lnCnt)
   878  		checkLeafNodeConnectedCount(t, sLeaf, lnCnt)
   879  
   880  		_, err = jsA.AddStream(&nats.StreamConfig{Name: "foo", Replicas: 1, Subjects: []string{"foo"}})
   881  		require_NoError(t, err)
   882  
   883  		// Enable jetstream in hub.
   884  		sdHub := t.TempDir()
   885  		jsEnabled := fmt.Sprintf(`{ domain: "%s", store_dir: '%s', max_mem: 100Mb, max_file: 100Mb }`, domain, sdHub)
   886  		require_NoError(t, os.WriteFile(confHub, []byte(fmt.Sprintf(tmplHub,
   887  			sHubUpd1.opts.Port,
   888  			"disabled",
   889  			jsEnabled,
   890  			sHubUpd1.opts.LeafNode.Port,
   891  			fmt.Sprintf(`default_js_domain: {A:"%s"}`, domain))), 0664))
   892  
   893  		sHubUpd1.Shutdown()
   894  		sHubUpd1.WaitForShutdown()
   895  		checkLeafNodeConnectedCount(t, sLeaf, 0)
   896  		sHubUpd2, _ := RunServerWithConfig(confHub)
   897  		defer sHubUpd2.Shutdown()
   898  
   899  		checkLeafNodeConnectedCount(t, sHubUpd2, lnCnt)
   900  		checkLeafNodeConnectedCount(t, sLeaf, lnCnt)
   901  
   902  		_, err = jsA.AddStream(&nats.StreamConfig{Name: "bar", Replicas: 1, Subjects: []string{"bar"}})
   903  		require_NoError(t, err)
   904  
   905  		// Enable jetstream in account A of hub
   906  		// This is a mis config, as you can't have it both ways, local jetstream but default to another one
   907  		require_NoError(t, os.WriteFile(confHub, []byte(fmt.Sprintf(tmplHub,
   908  			sHubUpd2.opts.Port,
   909  			"enabled",
   910  			jsEnabled,
   911  			sHubUpd2.opts.LeafNode.Port,
   912  			fmt.Sprintf(`default_js_domain: {A:"%s"}`, domain))), 0664))
   913  
   914  		if domain != _EMPTY_ {
   915  			// in case no domain name exists there are no additional guard rails, hence no error
   916  			// It is the users responsibility to get this edge case right
   917  			sHubUpd2.Shutdown()
   918  			sHubUpd2.WaitForShutdown()
   919  			checkLeafNodeConnectedCount(t, sLeaf, 0)
   920  			sHubUpd3, err := NewServer(LoadConfig(confHub))
   921  			sHubUpd3.Shutdown()
   922  
   923  			require_Error(t, err)
   924  			require_Contains(t, err.Error(), `default_js_domain contains account name "A" with enabled JetStream`)
   925  		}
   926  	}
   927  
   928  	t.Run("with-domain-sys", func(t *testing.T) {
   929  		test("domain", true)
   930  	})
   931  	t.Run("with-domain-nosys", func(t *testing.T) {
   932  		test("domain", false)
   933  	})
   934  	t.Run("no-domain", func(t *testing.T) {
   935  		test("", true)
   936  	})
   937  	t.Run("no-domain", func(t *testing.T) {
   938  		test("", false)
   939  	})
   940  }
   941  
   942  func TestJetStreamLeafNodeDefaultDomainJwtExplicit(t *testing.T) {
   943  	tmplHub := `
   944  listen: 127.0.0.1:%d
   945  operator: %s
   946  system_account: %s
   947  resolver: MEM
   948  resolver_preload: {
   949  	%s:%s
   950  	%s:%s
   951  }
   952  jetstream : disabled
   953  server_name: HUB
   954  leafnodes: {
   955  	listen: 127.0.0.1:%d
   956  }
   957  %s
   958  `
   959  
   960  	tmplL := `
   961  listen: 127.0.0.1:-1
   962  accounts :{
   963      A:{   jetstream: enable, users:[ {user:a1,password:a1}]},
   964      SYS:{ users:[ {user:s1,password:s1}]},
   965  }
   966  system_account: SYS
   967  jetstream: { domain: "%s", store_dir: '%s', max_mem: 100Mb, max_file: 100Mb }
   968  server_name: LEAF
   969  leafnodes: {
   970      remotes:[{url:nats://127.0.0.1:%d, account: A, credentials: '%s'},
   971  		     {url:nats://127.0.0.1:%d, account: SYS, credentials: '%s'}]
   972  }
   973  %s
   974  `
   975  
   976  	test := func(domain string) {
   977  		noDomainFix := ""
   978  		if domain == _EMPTY_ {
   979  			noDomainFix = `default_js_domain:{A:""}`
   980  		}
   981  
   982  		sysKp, syspub := createKey(t)
   983  		sysJwt := encodeClaim(t, jwt.NewAccountClaims(syspub), syspub)
   984  		sysCreds := newUser(t, sysKp)
   985  
   986  		aKp, aPub := createKey(t)
   987  		aClaim := jwt.NewAccountClaims(aPub)
   988  		aJwt := encodeClaim(t, aClaim, aPub)
   989  		aCreds := newUser(t, aKp)
   990  
   991  		confHub := createConfFile(t, []byte(fmt.Sprintf(tmplHub, -1, ojwt, syspub, syspub, sysJwt, aPub, aJwt, -1, "")))
   992  		sHub, _ := RunServerWithConfig(confHub)
   993  		defer sHub.Shutdown()
   994  
   995  		sdLeaf := t.TempDir()
   996  		confL := createConfFile(t, []byte(fmt.Sprintf(tmplL,
   997  			domain,
   998  			sdLeaf,
   999  			sHub.opts.LeafNode.Port,
  1000  			aCreds,
  1001  			sHub.opts.LeafNode.Port,
  1002  			sysCreds,
  1003  			noDomainFix)))
  1004  		sLeaf, _ := RunServerWithConfig(confL)
  1005  		defer sLeaf.Shutdown()
  1006  
  1007  		checkLeafNodeConnectedCount(t, sHub, 2)
  1008  		checkLeafNodeConnectedCount(t, sLeaf, 2)
  1009  
  1010  		ncA := natsConnect(t, fmt.Sprintf("nats://127.0.0.1:%d", sHub.opts.Port), createUserCreds(t, nil, aKp))
  1011  		defer ncA.Close()
  1012  		jsA, err := ncA.JetStream()
  1013  		require_NoError(t, err)
  1014  
  1015  		_, err = jsA.AddStream(&nats.StreamConfig{Name: "foo", Replicas: 1, Subjects: []string{"foo"}})
  1016  		require_True(t, err == nats.ErrNoResponders)
  1017  
  1018  		// Add in default domain and restart server
  1019  		require_NoError(t, os.WriteFile(confHub, []byte(fmt.Sprintf(tmplHub,
  1020  			sHub.opts.Port, ojwt, syspub, syspub, sysJwt, aPub, aJwt, sHub.opts.LeafNode.Port,
  1021  			fmt.Sprintf(`default_js_domain: {%s:"%s"}`, aPub, domain))), 0664))
  1022  
  1023  		sHub.Shutdown()
  1024  		sHub.WaitForShutdown()
  1025  		checkLeafNodeConnectedCount(t, sLeaf, 0)
  1026  		sHubUpd1, _ := RunServerWithConfig(confHub)
  1027  		defer sHubUpd1.Shutdown()
  1028  
  1029  		checkLeafNodeConnectedCount(t, sHubUpd1, 2)
  1030  		checkLeafNodeConnectedCount(t, sLeaf, 2)
  1031  
  1032  		_, err = jsA.AddStream(&nats.StreamConfig{Name: "bar", Replicas: 1, Subjects: []string{"bar"}})
  1033  		require_NoError(t, err)
  1034  	}
  1035  	t.Run("with-domain", func(t *testing.T) {
  1036  		test("domain")
  1037  	})
  1038  	t.Run("no-domain", func(t *testing.T) {
  1039  		test("")
  1040  	})
  1041  }
  1042  
  1043  func TestJetStreamLeafNodeDefaultDomainClusterBothEnds(t *testing.T) {
  1044  	// test to ensure that default domain functions when both ends of the leaf node connection are clusters
  1045  	tmplHub1 := `
  1046  listen: 127.0.0.1:-1
  1047  accounts :{
  1048      A:{ jetstream: enabled, users:[ {user:a1,password:a1}]},
  1049  	B:{ jetstream: enabled, users:[ {user:b1,password:b1}]}
  1050  }
  1051  jetstream : { domain: "DHUB", store_dir: '%s', max_mem: 100Mb, max_file: 100Mb }
  1052  server_name: HUB1
  1053  cluster: {
  1054  	name: HUB
  1055  	listen: 127.0.0.1:20134
  1056  	routes=[nats-route://127.0.0.1:20135]
  1057  }
  1058  leafnodes: {
  1059  	listen:127.0.0.1:-1
  1060  }
  1061  `
  1062  
  1063  	tmplHub2 := `
  1064  listen: 127.0.0.1:-1
  1065  accounts :{
  1066      A:{ jetstream: enabled, users:[ {user:a1,password:a1}]},
  1067  	B:{ jetstream: enabled, users:[ {user:b1,password:b1}]}
  1068  }
  1069  jetstream : { domain: "DHUB", store_dir: '%s', max_mem: 100Mb, max_file: 100Mb }
  1070  server_name: HUB2
  1071  cluster: {
  1072  	name: HUB
  1073  	listen: 127.0.0.1:20135
  1074  	routes=[nats-route://127.0.0.1:20134]
  1075  }
  1076  leafnodes: {
  1077  	listen:127.0.0.1:-1
  1078  }
  1079  `
  1080  
  1081  	tmplL1 := `
  1082  listen: 127.0.0.1:-1
  1083  accounts :{
  1084      A:{ jetstream: enabled,  users:[ {user:a1,password:a1}]},
  1085  	B:{ jetstream: disabled, users:[ {user:b1,password:b1}]}
  1086  }
  1087  jetstream: { domain: "DLEAF", store_dir: '%s', max_mem: 100Mb, max_file: 100Mb }
  1088  server_name: LEAF1
  1089  cluster: {
  1090  	name: LEAF
  1091  	listen: 127.0.0.1:20136
  1092  	routes=[nats-route://127.0.0.1:20137]
  1093  }
  1094  leafnodes: {
  1095      remotes:[{url:nats://a1:a1@127.0.0.1:%d, account: A},{url:nats://b1:b1@127.0.0.1:%d, account: B}]
  1096  }
  1097  default_js_domain: {B:"DHUB"}
  1098  `
  1099  
  1100  	tmplL2 := `
  1101  listen: 127.0.0.1:-1
  1102  accounts :{
  1103      A:{ jetstream: enabled,  users:[ {user:a1,password:a1}]},
  1104  	B:{ jetstream: disabled, users:[ {user:b1,password:b1}]}
  1105  }
  1106  jetstream: { domain: "DLEAF", store_dir: '%s', max_mem: 100Mb, max_file: 100Mb }
  1107  server_name: LEAF2
  1108  cluster: {
  1109  	name: LEAF
  1110  	listen: 127.0.0.1:20137
  1111  	routes=[nats-route://127.0.0.1:20136]
  1112  }
  1113  leafnodes: {
  1114      remotes:[{url:nats://a1:a1@127.0.0.1:%d, account: A},{url:nats://b1:b1@127.0.0.1:%d, account: B}]
  1115  }
  1116  default_js_domain: {B:"DHUB"}
  1117  `
  1118  
  1119  	sd1 := t.TempDir()
  1120  	confHub1 := createConfFile(t, []byte(fmt.Sprintf(tmplHub1, sd1)))
  1121  	sHub1, _ := RunServerWithConfig(confHub1)
  1122  	defer sHub1.Shutdown()
  1123  
  1124  	sd2 := t.TempDir()
  1125  	confHub2 := createConfFile(t, []byte(fmt.Sprintf(tmplHub2, sd2)))
  1126  	sHub2, _ := RunServerWithConfig(confHub2)
  1127  	defer sHub2.Shutdown()
  1128  
  1129  	checkClusterFormed(t, sHub1, sHub2)
  1130  	c1 := cluster{t: t, servers: []*Server{sHub1, sHub2}}
  1131  	c1.waitOnPeerCount(2)
  1132  
  1133  	sd3 := t.TempDir()
  1134  	confLeaf1 := createConfFile(t, []byte(fmt.Sprintf(tmplL1, sd3, sHub1.getOpts().LeafNode.Port, sHub1.getOpts().LeafNode.Port)))
  1135  	sLeaf1, _ := RunServerWithConfig(confLeaf1)
  1136  	defer sLeaf1.Shutdown()
  1137  
  1138  	confLeaf2 := createConfFile(t, []byte(fmt.Sprintf(tmplL2, sd3, sHub1.getOpts().LeafNode.Port, sHub1.getOpts().LeafNode.Port)))
  1139  	sLeaf2, _ := RunServerWithConfig(confLeaf2)
  1140  	defer sLeaf2.Shutdown()
  1141  
  1142  	checkClusterFormed(t, sLeaf1, sLeaf2)
  1143  	c2 := cluster{t: t, servers: []*Server{sLeaf1, sLeaf2}}
  1144  	c2.waitOnPeerCount(2)
  1145  
  1146  	checkLeafNodeConnectedCount(t, sHub1, 4)
  1147  	checkLeafNodeConnectedCount(t, sLeaf1, 2)
  1148  	checkLeafNodeConnectedCount(t, sLeaf2, 2)
  1149  
  1150  	ncB := natsConnect(t, fmt.Sprintf("nats://b1:b1@127.0.0.1:%d", sLeaf1.getOpts().Port))
  1151  	defer ncB.Close()
  1152  	jsB1, err := ncB.JetStream()
  1153  	require_NoError(t, err)
  1154  	si, err := jsB1.AddStream(&nats.StreamConfig{Name: "foo", Replicas: 1, Subjects: []string{"foo"}})
  1155  	require_NoError(t, err)
  1156  	require_Equal(t, si.Cluster.Name, "HUB")
  1157  
  1158  	jsB2, err := ncB.JetStream(nats.Domain("DHUB"))
  1159  	require_NoError(t, err)
  1160  	si, err = jsB2.AddStream(&nats.StreamConfig{Name: "bar", Replicas: 1, Subjects: []string{"bar"}})
  1161  	require_NoError(t, err)
  1162  	require_Equal(t, si.Cluster.Name, "HUB")
  1163  }
  1164  
  1165  func TestJetStreamLeafNodeSvcImportExportCycle(t *testing.T) {
  1166  	accounts := `
  1167  	accounts {
  1168  		SYS: {
  1169  			users: [{user: admin, password: admin}]
  1170  		}
  1171  		LEAF_USER: {
  1172  			users: [{user: leaf_user, password: leaf_user}]
  1173  			imports: [
  1174  				{service: {account: LEAF_INGRESS, subject: "foo"}}
  1175  				{service: {account: LEAF_INGRESS, subject: "_INBOX.>"}}
  1176  				{service: {account: LEAF_INGRESS, subject: "$JS.leaf.API.>"}, to: "JS.leaf_ingress@leaf.API.>" }
  1177  			]
  1178  			jetstream: enabled
  1179  		}
  1180  		LEAF_INGRESS: {
  1181  			users: [{user: leaf_ingress, password: leaf_ingress}]
  1182  			exports: [
  1183  				{service: "foo", accounts: [LEAF_USER]}
  1184  				{service: "_INBOX.>", accounts: [LEAF_USER]}
  1185  				{service: "$JS.leaf.API.>", response_type: "stream", accounts: [LEAF_USER]}
  1186  			]
  1187  			imports: [
  1188  			]
  1189  			jetstream: enabled
  1190  		}
  1191  	}
  1192  	system_account: SYS
  1193  	`
  1194  
  1195  	hconf := createConfFile(t, []byte(fmt.Sprintf(`
  1196  	%s
  1197  	listen: "127.0.0.1:-1"
  1198  	leafnodes {
  1199  		listen: "127.0.0.1:-1"
  1200  	}
  1201  	`, accounts)))
  1202  	defer os.Remove(hconf)
  1203  	s, o := RunServerWithConfig(hconf)
  1204  	defer s.Shutdown()
  1205  
  1206  	lconf := createConfFile(t, []byte(fmt.Sprintf(`
  1207  	%s
  1208  	server_name: leaf-server
  1209  	jetstream {
  1210  		store_dir: '%s'
  1211  		domain=leaf
  1212  	}
  1213  
  1214  	listen: "127.0.0.1:-1"
  1215  	leafnodes {
  1216  		remotes = [
  1217  			{
  1218  				urls: ["nats-leaf://leaf_ingress:leaf_ingress@127.0.0.1:%v"]
  1219  				account: "LEAF_INGRESS"
  1220  			}
  1221  		]
  1222  	}
  1223  	`, accounts, t.TempDir(), o.LeafNode.Port)))
  1224  	defer os.Remove(lconf)
  1225  	sl, so := RunServerWithConfig(lconf)
  1226  	defer sl.Shutdown()
  1227  
  1228  	checkLeafNodeConnected(t, sl)
  1229  
  1230  	nc := natsConnect(t, fmt.Sprintf("nats://leaf_user:leaf_user@127.0.0.1:%v", so.Port))
  1231  	defer nc.Close()
  1232  
  1233  	js, _ := nc.JetStream(nats.APIPrefix("JS.leaf_ingress@leaf.API."))
  1234  
  1235  	_, err := js.AddStream(&nats.StreamConfig{
  1236  		Name:     "TEST",
  1237  		Subjects: []string{"foo"},
  1238  		Storage:  nats.FileStorage,
  1239  	})
  1240  	require_NoError(t, err)
  1241  
  1242  	_, err = js.Publish("foo", []byte("msg"))
  1243  	require_NoError(t, err)
  1244  }