Repository: nats-io/nats-server (go)

Issue:
expose account specific monitoring/debugging information as system service
This covers SUBSZ and CONNZ. 
Also add an INFO so to retrieve state associated with the account struct

Pull request:
[Adding] Accountz monitoring endpoint and INFO monitoring req subject
Returned imports/exports are formatted like jwt exports imports, even if they originating account is from config.
Fixes #1604

Signed-off-by: Matthias Hanel <mh@synadia.com>

I named the account specific subject INFO as it makes more sense than ACCOUNTZ.
ForCONNZ/SUBSZ we remain to have the plural even when scoped to the account, not so much for account itself.

Also setting update time on every update and account creation. (avoids empty values)
For consistency, setting incomplete when expired (dummy) account is created.
This is primarily to make the returned json make sense and look somewhat similar no matter the origin (jwt/cfg)


```
http://localhost:7222/accountz

{
  "server_id": "NDSVJWYHPIXEYV3PS66X4V2PUWWYVQVPQUJ35FZJRI5PN6QMWBHJ2MZH",
  "now": "2020-09-23T16:18:53.199956-04:00",
  "accounts": [
    "SYS",
    "$G",
    "USERS"
  ]
}
```


```
http://localhost:7222/accountz?acc=SYS

{
  "server_id": "NDSVJWYHPIXEYV3PS66X4V2PUWWYVQVPQUJ35FZJRI5PN6QMWBHJ2MZH",
  "now": "2020-09-23T16:19:56.311826-04:00",
  "account_detail": {
    "account_name": "SYS",
    "update_time": "2020-09-23T16:18:44.089969-04:00",
    "expired": false,
    "complete": true,
    "jetstream_enabled": false,
    "leafnode_connections": 0,
    "client_connections": 0,
    "subscriptions": 32,
    "exports": [
      {
        "subject": "$SYS.DEBUG.SUBSCRIBERS",
        "type": "service",
        "response_type": "Singleton",
        "approved_accounts": []
      }
    ],
    "imports": []
  }
}
```

Patch:
diff --git a/server/accounts.go b/server/accounts.go
index e329541e..83615457 100644
--- a/server/accounts.go
+++ b/server/accounts.go
@@ -2618,6 +2618,7 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim
 			a.jsLimits = nil
 		}
 	}
+	a.updated = time.Now()
 	a.mu.Unlock()
 
 	clients := gatherClients()
diff --git a/server/events.go b/server/events.go
index 71c558cb..97b3f931 100644
--- a/server/events.go
+++ b/server/events.go
@@ -639,6 +639,10 @@ func (s *Server) initEventTracking() {
 			optz := &LeafzEventOptions{}
 			s.zReq(reply, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) { return s.Leafz(&optz.LeafzOptions) })
 		},
+		"ACCOUNTZ": func(sub *subscription, _ *client, subject, reply string, msg []byte) {
+			optz := &AccountzEventOptions{}
+			s.zReq(reply, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) { return s.Accountz(&optz.AccountzOptions) })
+		},
 	}
 	for name, req := range monSrvc {
 		subject = fmt.Sprintf(serverDirectReqSubj, s.info.ID, name)
@@ -681,6 +685,16 @@ func (s *Server) initEventTracking() {
 				}
 			})
 		},
+		"INFO": func(sub *subscription, _ *client, subject, reply string, msg []byte) {
+			optz := &AccInfoEventOptions{}
+			s.zReq(reply, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) {
+				if acc, err := extractAccount(subject); err != nil {
+					return nil, err
+				} else {
+					return s.accountInfo(acc)
+				}
+			})
+		},
 		"CONNS": s.connsRequest,
 	}
 	for name, req := range monAccSrvc {
@@ -918,7 +932,12 @@ type EventFilterOptions struct {
 // StatszEventOptions are options passed to Statsz
 type StatszEventOptions struct {
 	// No actual options yet
+	EventFilterOptions
+}
 
+// Options for account Info
+type AccInfoEventOptions struct {
+	// No actual options yet
 	EventFilterOptions
 }
 
@@ -958,6 +977,12 @@ type LeafzEventOptions struct {
 	EventFilterOptions
 }
 
+// In the context of system events, AccountzEventOptions are options passed to Accountz
+type AccountzEventOptions struct {
+	AccountzOptions
+	EventFilterOptions
+}
+
 // returns true if the request does NOT apply to this server and can be ignored.
 // DO NOT hold the server lock when
 func (s *Server) filterRequest(fOpts *EventFilterOptions) bool {
diff --git a/server/leafnode.go b/server/leafnode.go
index f7d7cd84..358d07c3 100644
--- a/server/leafnode.go
+++ b/server/leafnode.go
@@ -1035,9 +1035,6 @@ func (c *client) processLeafNodeConnect(s *Server, arg []byte, lang string) erro
 		return ErrWrongGateway
 	}
 
-	// Check for stale connection from same server/account
-	c.replaceOldLeafNodeConnIfNeeded(s, proto)
-
 	// Leaf Nodes do not do echo or verbose or pedantic.
 	c.opts.Verbose = false
 	c.opts.Echo = false
@@ -1071,42 +1068,6 @@ func (c *client) processLeafNodeConnect(s *Server, arg []byte, lang string) erro
 	return nil
 }
 
-// Invoked from a server accepting a leafnode connection. It looks for a possible
-// existing leafnode connection from the same server with the same account, and
-// if it finds one, closes it so that the new one is accepted and not reported as
-// forming a cycle.
-//
-// This must be invoked for LEAF client types, and on the server accepting the connection.
-//
-// No server nor client lock held on entry.
-func (c *client) replaceOldLeafNodeConnIfNeeded(s *Server, connInfo *leafConnectInfo) {
-	var accName string
-	c.mu.Lock()
-	if c.acc != nil {
-		accName = c.acc.Name
-	}
-	c.mu.Unlock()
-
-	var old *client
-	s.mu.Lock()
-	for _, ol := range s.leafs {
-		ol.mu.Lock()
-		// We check for empty because in some test we may send empty CONNECT{}
-		if ol.opts.Name == connInfo.Name && connInfo.Name != _EMPTY_ && ol.acc.Name == accName {
-			old = ol
-		}
-		ol.mu.Unlock()
-		if old != nil {
-			break
-		}
-	}
-	s.mu.Unlock()
-	if old != nil {
-		old.Warnf("Replacing connection from same server")
-		old.closeConnection(ReadError)
-	}
-}
-
 // Returns the remote cluster name. This is set only once so does not require a lock.
 func (c *client) remoteCluster() string {
 	if c.leaf == nil {
diff --git a/server/monitor.go b/server/monitor.go
index 279c7612..4401cc26 100644
--- a/server/monitor.go
+++ b/server/monitor.go
@@ -27,6 +27,7 @@ import (
 	"sync/atomic"
 	"time"
 
+	"github.com/nats-io/jwt/v2"
 	"github.com/nats-io/nats-server/v2/server/pse"
 )
 
@@ -1144,6 +1145,7 @@ func (s *Server) HandleRoot(w http.ResponseWriter, r *http.Request) {
 	<a href=.%s>gatewayz</a><br/>
 	<a href=.%s>leafz</a><br/>
 	<a href=.%s>subsz</a><br/>
+	<a href=.%s>accountz</a><br/>
     <br/>
     <a href=https://docs.nats.io/nats-server/configuration/monitoring.html>help</a>
   </body>
@@ -1154,6 +1156,7 @@ func (s *Server) HandleRoot(w http.ResponseWriter, r *http.Request) {
 		s.basePath(GatewayzPath),
 		s.basePath(LeafzPath),
 		s.basePath(SubszPath),
+		s.basePath(AccountzPath),
 	)
 }
 
@@ -1910,3 +1913,161 @@ func (reason ClosedState) String() string {
 
 	return "Unknown State"
 }
+
+// LeafzOptions are options passed to Leafz
+type AccountzOptions struct {
+	// Account indicates that Accountz will return details for the account
+	Account string `json:"account"`
+}
+
+type ExtImport struct {
+	jwt.Import
+	Invalid bool `json:"invalid"`
+}
+
+type ExtExport struct {
+	jwt.Export
+	ApprovedAccounts []string `json:"approved_accounts,omitempty"`
+}
+
+type AccountInfo struct {
+	AccountName string             `json:"account_name"`
+	LastUpdate  time.Time          `json:"update_time,omitempty"`
+	Expired     bool               `json:"expired"`
+	Complete    bool               `json:"complete"`
+	JetStream   bool               `json:"jetstream_enabled"`
+	LeafCnt     int                `json:"leafnode_connections"`
+	ClientCnt   int                `json:"client_connections"`
+	SubCnt      uint32             `json:"subscriptions"`
+	Exports     []ExtExport        `json:"exports"`
+	Imports     []ExtImport        `json:"imports"`
+	Jwt         string             `json:"jwt,omitempty"`
+	Claim       *jwt.AccountClaims `json:"decoded_jwt,omitempty"`
+}
+
+type Accountz struct {
+	ID       string       `json:"server_id"`
+	Now      time.Time    `json:"now"`
+	Accounts []string     `json:"accounts,omitempty"`
+	Account  *AccountInfo `json:"account_detail,omitempty"`
+}
+
+// HandleAccountz process HTTP requests for account information.
+func (s *Server) HandleAccountz(w http.ResponseWriter, r *http.Request) {
+	s.mu.Lock()
+	s.httpReqStats[AccountzPath]++
+	s.mu.Unlock()
+	if l, err := s.Accountz(&AccountzOptions{r.URL.Query().Get("acc")}); err != nil {
+		w.WriteHeader(http.StatusBadRequest)
+		w.Write([]byte(err.Error()))
+	} else if b, err := json.MarshalIndent(l, "", "  "); err != nil {
+		s.Errorf("Error marshaling response to %s request: %v", AccountzPath, err)
+		w.WriteHeader(http.StatusBadRequest)
+		w.Write([]byte(err.Error()))
+	} else {
+		ResponseHandler(w, r, b) // Handle response
+	}
+}
+
+func (s *Server) Accountz(optz *AccountzOptions) (*Accountz, error) {
+	a := &Accountz{
+		ID:  s.ID(),
+		Now: time.Now(),
+	}
+	if optz.Account == "" {
+		a.Accounts = []string{}
+		s.accounts.Range(func(key, value interface{}) bool {
+			a.Accounts = append(a.Accounts, key.(string))
+			return true
+		})
+		return a, nil
+	} else if aInfo, err := s.accountInfo(optz.Account); err != nil {
+		return nil, err
+	} else {
+		a.Account = aInfo
+		return a, nil
+	}
+}
+
+func (s *Server) accountInfo(accName string) (*AccountInfo, error) {
+	var a *Account
+	if v, ok := s.accounts.Load(accName); !ok {
+		return nil, fmt.Errorf("Account %s does not exist", accName)
+	} else {
+		a = v.(*Account)
+	}
+	a.mu.RLock()
+	defer a.mu.RUnlock()
+	claim, _ := jwt.DecodeAccountClaims(a.claimJWT) // ignore error
+	exports := []ExtExport{}
+	for k, v := range a.exports.services {
+		e := ExtExport{
+			Export: jwt.Export{
+				Subject:      jwt.Subject(k),
+				Type:         jwt.Service,
+				TokenReq:     v.tokenReq,
+				ResponseType: jwt.ResponseType(v.respType.String()),
+			},
+			ApprovedAccounts: []string{},
+		}
+		for name := range v.approved {
+			e.ApprovedAccounts = append(e.ApprovedAccounts, name)
+		}
+		exports = append(exports, e)
+	}
+	for k, v := range a.exports.streams {
+		e := ExtExport{
+			Export: jwt.Export{
+				Subject:  jwt.Subject(k),
+				Type:     jwt.Stream,
+				TokenReq: v.tokenReq,
+			},
+			ApprovedAccounts: []string{},
+		}
+		for name := range v.approved {
+			e.ApprovedAccounts = append(e.ApprovedAccounts, name)
+		}
+		exports = append(exports, e)
+	}
+	imports := []ExtImport{}
+	for _, v := range a.imports.streams {
+		to := ""
+		if v.prefix != "" {
+			to = v.prefix + "." + v.from
+		}
+		imports = append(imports, ExtImport{
+			Import: jwt.Import{
+				Subject: jwt.Subject(v.from),
+				Account: v.acc.Name,
+				Type:    jwt.Stream,
+				To:      jwt.Subject(to),
+			},
+			Invalid: v.invalid,
+		})
+	}
+	for _, v := range a.imports.services {
+		imports = append(imports, ExtImport{
+			Import: jwt.Import{
+				Subject: jwt.Subject(v.from),
+				Account: v.acc.Name,
+				Type:    jwt.Service,
+				To:      jwt.Subject(v.to),
+			},
+			Invalid: v.invalid,
+		})
+	}
+	return &AccountInfo{
+		accName,
+		a.updated,
+		a.expired,
+		!a.incomplete,
+		a.js != nil,
+		a.numLocalLeafNodes(),
+		a.numLocalConnections(),
+		a.sl.Count(),
+		exports,
+		imports,
+		a.claimJWT,
+		claim,
+	}, nil
+}
diff --git a/server/route.go b/server/route.go
index 8609f38f..fcd3014b 100644
--- a/server/route.go
+++ b/server/route.go
@@ -1028,6 +1028,7 @@ func (c *client) processRemoteSub(argo []byte, hasOrigin bool) (err error) {
 		if acc, isNew = srv.LookupOrRegisterAccount(accountName); isNew && expire {
 			acc.mu.Lock()
 			acc.expired = true
+			acc.incomplete = true
 			acc.mu.Unlock()
 		}
 	}
diff --git a/server/server.go b/server/server.go
index c6ddb819..fa6b77e8 100644
--- a/server/server.go
+++ b/server/server.go
@@ -640,7 +640,7 @@ func (s *Server) configureAccounts() error {
 			acc.ic.acc = acc
 			acc.addAllServiceImportSubs()
 		}
-
+		acc.updated = time.Now()
 		return true
 	})
 
@@ -1157,6 +1157,7 @@ func (s *Server) registerAccountNoLock(acc *Account) *Account {
 		acc.lqws = make(map[string]int32)
 	}
 	acc.srv = s
+	acc.updated = time.Now()
 	acc.mu.Unlock()
 	s.accounts.Store(acc.Name, acc)
 	s.tmpAccounts.Delete(acc.Name)
@@ -1205,7 +1206,7 @@ func (s *Server) LookupAccount(name string) (*Account, error) {
 // Lock MUST NOT be held upon entry.
 func (s *Server) updateAccount(acc *Account) error {
 	// TODO(dlc) - Make configurable
-	if time.Since(acc.updated) < time.Second {
+	if !acc.incomplete && time.Since(acc.updated) < time.Second {
 		s.Debugf("Requested account update for [%s] ignored, too soon", acc.Name)
 		return ErrAccountResolverUpdateTooSoon
 	}
@@ -1228,7 +1229,6 @@ func (s *Server) updateAccountWithClaimJWT(acc *Account, claimJWT string) error
 	}
 	accClaims, _, err := s.verifyAccountClaims(claimJWT)
 	if err == nil && accClaims != nil {
-		acc.updated = time.Now()
 		acc.mu.Lock()
 		if acc.Issuer == "" {
 			acc.Issuer = accClaims.Issuer
@@ -1908,6 +1908,7 @@ const (
 	LeafzPath    = "/leafz"
 	SubszPath    = "/subsz"
 	StackszPath  = "/stacksz"
+	AccountzPath = "/accountz"
 )
 
 func (s *Server) basePath(p string) string {
@@ -1985,6 +1986,8 @@ func (s *Server) startMonitoring(secure bool) error {
 	mux.HandleFunc(s.basePath("/subscriptionsz"), s.HandleSubsz)
 	// Stacksz
 	mux.HandleFunc(s.basePath(StackszPath), s.HandleStacksz)
+	// Accountz
+	mux.HandleFunc(s.basePath(AccountzPath), s.HandleAccountz)
 
 	// Do not set a WriteTimeout because it could cause cURL/browser
 	// to return empty response or unable to display page if the


Test patch:
diff --git a/server/events_test.go b/server/events_test.go
index 3c204b0b..ced31d7a 100644
--- a/server/events_test.go
+++ b/server/events_test.go
@@ -1258,6 +1258,112 @@ func TestAccountReqMonitoring(t *testing.T) {
 	}
 }
 
+func TestAccountReqInfo(t *testing.T) {
+	s, opts := runTrustedServer(t)
+	defer s.Shutdown()
+	sacc, sakp := createAccount(s)
+	s.setSystemAccount(sacc)
+	// Let's create an account with service export.
+	akp, _ := nkeys.CreateAccount()
+	pub1, _ := akp.PublicKey()
+	nac1 := jwt.NewAccountClaims(pub1)
+	nac1.Exports.Add(&jwt.Export{Subject: "req.*", Type: jwt.Service})
+	ajwt1, _ := nac1.Encode(oKp)
+	addAccountToMemResolver(s, pub1, ajwt1)
+	s.LookupAccount(pub1)
+	info1 := fmt.Sprintf(accReqSubj, pub1, "INFO")
+	// Now add an account with service imports.
+	akp2, _ := nkeys.CreateAccount()
+	pub2, _ := akp2.PublicKey()
+	nac2 := jwt.NewAccountClaims(pub2)
+	nac2.Imports.Add(&jwt.Import{Account: pub1, Subject: "req.1", Type: jwt.Service})
+	ajwt2, _ := nac2.Encode(oKp)
+	addAccountToMemResolver(s, pub2, ajwt2)
+	s.LookupAccount(pub2)
+	info2 := fmt.Sprintf(accReqSubj, pub2, "INFO")
+	// Create system account connection to query
+	url := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
+	ncSys, err := nats.Connect(url, createUserCreds(t, s, sakp))
+	if err != nil {
+		t.Fatalf("Error on connect: %v", err)
+	}
+	defer ncSys.Close()
+	checkCommon := func(info *AccountInfo, srv *ServerInfo, pub, jwt string) {
+		if info.Complete != true {
+			t.Fatalf("Unexpected value: %v", info.Complete)
+		} else if info.Expired != false {
+			t.Fatalf("Unexpected value: %v", info.Expired)
+		} else if info.JetStream != false {
+			t.Fatalf("Unexpected value: %v", info.JetStream)
+		} else if info.ClientCnt != 0 {
+			t.Fatalf("Unexpected value: %v", info.ClientCnt)
+		} else if info.AccountName != pub {
+			t.Fatalf("Unexpected value: %v", info.AccountName)
+		} else if info.LeafCnt != 0 {
+			t.Fatalf("Unexpected value: %v", info.LeafCnt)
+		} else if info.Jwt != jwt {
+			t.Fatalf("Unexpected value: %v", info.Jwt)
+		} else if srv.Cluster != "abc" {
+			t.Fatalf("Unexpected value: %v", srv.Cluster)
+		} else if srv.Name != s.Name() {
+			t.Fatalf("Unexpected value: %v", srv.Name)
+		} else if srv.Host != opts.Host {
+			t.Fatalf("Unexpected value: %v", srv.Host)
+		} else if srv.Seq < 1 {
+			t.Fatalf("Unexpected value: %v", srv.Seq)
+		}
+	}
+	info := AccountInfo{}
+	srv := ServerInfo{}
+	msg := struct {
+		Data *AccountInfo `json:"data"`
+		Srv  *ServerInfo  `json:"server"`
+	}{
+		&info,
+		&srv,
+	}
+	if resp, err := ncSys.Request(info1, nil, time.Second); err != nil {
+		t.Fatalf("Error on request: %v", err)
+	} else if err := json.Unmarshal(resp.Data, &msg); err != nil {
+		t.Fatalf("Unmarshalling failed: %v", err)
+	} else if len(info.Exports) != 1 {
+		t.Fatalf("Unexpected value: %v", info.Exports)
+	} else if len(info.Imports) != 0 {
+		t.Fatalf("Unexpected value: %v", info.Imports)
+	} else if info.Exports[0].Subject != "req.*" {
+		t.Fatalf("Unexpected value: %v", info.Exports)
+	} else if info.Exports[0].Type != jwt.Service {
+		t.Fatalf("Unexpected value: %v", info.Exports)
+	} else if info.Exports[0].ResponseType != jwt.ResponseTypeSingleton {
+		t.Fatalf("Unexpected value: %v", info.Exports)
+	} else if info.SubCnt != 0 {
+		t.Fatalf("Unexpected value: %v", info.SubCnt)
+	} else {
+		checkCommon(&info, &srv, pub1, ajwt1)
+	}
+	info = AccountInfo{}
+	srv = ServerInfo{}
+	if resp, err := ncSys.Request(info2, nil, time.Second); err != nil {
+		t.Fatalf("Error on request: %v", err)
+	} else if err := json.Unmarshal(resp.Data, &msg); err != nil {
+		t.Fatalf("Unmarshalling failed: %v", err)
+	} else if len(info.Exports) != 0 {
+		t.Fatalf("Unexpected value: %v", info.Exports)
+	} else if len(info.Imports) != 1 {
+		t.Fatalf("Unexpected value: %v", info.Imports)
+	} else if info.Imports[0].Subject != "req.1" {
+		t.Fatalf("Unexpected value: %v", info.Exports)
+	} else if info.Imports[0].Type != jwt.Service {
+		t.Fatalf("Unexpected value: %v", info.Exports)
+	} else if info.Imports[0].Account != pub1 {
+		t.Fatalf("Unexpected value: %v", info.Exports)
+	} else if info.SubCnt != 1 {
+		t.Fatalf("Unexpected value: %v", info.SubCnt)
+	} else {
+		checkCommon(&info, &srv, pub2, ajwt2)
+	}
+}
+
 func TestAccountClaimsUpdatesWithServiceImports(t *testing.T) {
 	s, opts := runTrustedServer(t)
 	defer s.Shutdown()
@@ -1453,7 +1559,7 @@ func TestSystemAccountWithGateways(t *testing.T) {
 
 	// If this tests fails with wrong number after 10 seconds we may have
 	// added a new inititial subscription for the eventing system.
-	checkExpectedSubs(t, 30, sa)
+	checkExpectedSubs(t, 33, sa)
 
 	// Create a client on B and see if we receive the event
 	urlb := fmt.Sprintf("nats://%s:%d", ob.Host, ob.Port)
@@ -1775,7 +1881,7 @@ func TestServerEventsPingMonitorz(t *testing.T) {
 	sa, _, sb, optsB, akp := runTrustedCluster(t)
 	defer sa.Shutdown()
 	defer sb.Shutdown()
-
+	sysAcc, _ := akp.PublicKey()
 	url := fmt.Sprintf("nats://%s:%d", optsB.Host, optsB.Port)
 	nc, err := nats.Connect(url, createUserCreds(t, sb, akp))
 	if err != nil {
@@ -1814,6 +1920,8 @@ func TestServerEventsPingMonitorz(t *testing.T) {
 			[]string{"now", "outbound_gateways", "inbound_gateways"}},
 		{"LEAFZ", &LeafzOptions{}, &Leafz{},
 			[]string{"now", "leafs"}},
+		{"ACCOUNTZ", &AccountzOptions{}, &Accountz{},
+			[]string{"now", "accounts"}},
 
 		{"SUBSZ", &SubszOptions{Limit: 5}, &Subsz{},
 			[]string{"num_subscriptions", "num_cache"}},
@@ -1825,6 +1933,8 @@ func TestServerEventsPingMonitorz(t *testing.T) {
 			[]string{"now", "outbound_gateways", "inbound_gateways"}},
 		{"LEAFZ", &LeafzOptions{Subscriptions: true}, &Leafz{},
 			[]string{"now", "leafs"}},
+		{"ACCOUNTZ", &AccountzOptions{Account: sysAcc}, &Accountz{},
+			[]string{"now", "account_detail"}},
 
 		{"ROUTEZ", json.RawMessage(`{"cluster":""}`), &Routez{},
 			[]string{"now", "routes"}},
diff --git a/server/jwt_test.go b/server/jwt_test.go
index 8424095d..2c740295 100644
--- a/server/jwt_test.go
+++ b/server/jwt_test.go
@@ -2994,6 +2994,7 @@ func TestJWTAccountLimitsMaxConnsAfterExpired(t *testing.T) {
 	acc, _ := s.LookupAccount(fooPub)
 	acc.mu.Lock()
 	acc.expired = true
+	acc.updated = time.Now().Add(-2 * time.Second) // work around updating to quickly
 	acc.mu.Unlock()
 
 	// Now update with new expiration and max connections lowered to 2
diff --git a/server/leafnode_test.go b/server/leafnode_test.go
index d9c0f626..0555908b 100644
--- a/server/leafnode_test.go
+++ b/server/leafnode_test.go
@@ -22,7 +22,6 @@ import (
 	"net/url"
 	"os"
 	"strings"
-	"sync"
 	"sync/atomic"
 	"testing"
 	"time"
@@ -1651,154 +1650,3 @@ func TestLeafNodeOriginClusterInfo(t *testing.T) {
 		t.Fatalf("Expected a different id, got the same")
 	}
 }
-
-type proxyAcceptDetectFailureLate struct {
-	sync.Mutex
-	wg         sync.WaitGroup
-	acceptPort int
-	l          net.Listener
-	srvs       []net.Conn
-	leaf       net.Conn
-}
-
-func (p *proxyAcceptDetectFailureLate) run(t *testing.T) int {
-	l, err := natsListen("tcp", "127.0.0.1:0")
-	if err != nil {
-		t.Fatalf("Error on listen: %v", err)
-	}
-	p.Lock()
-	p.l = l
-	p.Unlock()
-	port := l.Addr().(*net.TCPAddr).Port
-	p.wg.Add(1)
-	go func() {
-		defer p.wg.Done()
-		defer l.Close()
-		defer func() {
-			p.Lock()
-			for _, c := range p.srvs {
-				c.Close()
-			}
-			p.Unlock()
-		}()
-		for {
-			c, err := l.Accept()
-			if err != nil {
-				return
-			}
-			srv, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", p.acceptPort))
-			if err != nil {
-				return
-			}
-			p.Lock()
-			p.leaf = c
-			p.srvs = append(p.srvs, srv)
-			p.Unlock()
-
-			transfer := func(c1, c2 net.Conn) {
-				var buf [1024]byte
-				for {
-					n, err := c1.Read(buf[:])
-					if err != nil {
-						return
-					}
-					if _, err := c2.Write(buf[:n]); err != nil {
-						return
-					}
-				}
-			}
-
-			go transfer(srv, c)
-			go transfer(c, srv)
-		}
-	}()
-	return port
-}
-
-func (p *proxyAcceptDetectFailureLate) close() {
-	p.Lock()
-	p.l.Close()
-	p.Unlock()
-
-	p.wg.Wait()
-}
-
-type oldConnReplacedLogger struct {
-	DummyLogger
-	errCh  chan string
-	warnCh chan string
-}
-
-func (l *oldConnReplacedLogger) Errorf(format string, v ...interface{}) {
-	select {
-	case l.errCh <- fmt.Sprintf(format, v...):
-	default:
-	}
-}
-
-func (l *oldConnReplacedLogger) Warnf(format string, v ...interface{}) {
-	select {
-	case l.warnCh <- fmt.Sprintf(format, v...):
-	default:
-	}
-}
-
-// This test will simulate that the accept side does not detect the connection
-// has been closed early enough. The soliciting side will attempt to reconnect
-// and we should not be getting the "loop detected" error.
-func TestLeafNodeLoopDetectedDueToReconnect(t *testing.T) {
-	o := DefaultOptions()
-	o.LeafNode.Host = "127.0.0.1"
-	o.LeafNode.Port = -1
-	s := RunServer(o)
-	defer s.Shutdown()
-
-	l := &oldConnReplacedLogger{errCh: make(chan string, 10), warnCh: make(chan string, 10)}
-	s.SetLogger(l, false, false)
-
-	p := &proxyAcceptDetectFailureLate{acceptPort: o.LeafNode.Port}
-	defer p.close()
-	port := p.run(t)
-
-	aurl, err := url.Parse(fmt.Sprintf("nats://127.0.0.1:%d", port))
-	if err != nil {
-		t.Fatalf("Error parsing url: %v", err)
-	}
-	ol := DefaultOptions()
-	ol.Cluster.Name = "cde"
-	ol.LeafNode.ReconnectInterval = 50 * time.Millisecond
-	ol.LeafNode.Remotes = []*RemoteLeafOpts{&RemoteLeafOpts{URLs: []*url.URL{aurl}}}
-	sl := RunServer(ol)
-	defer sl.Shutdown()
-
-	checkLeafNodeConnected(t, s)
-	checkLeafNodeConnected(t, sl)
-
-	// Cause disconnect client side...
-	p.Lock()
-	p.leaf.Close()
-	p.Unlock()
-
-	// Make sure we did not get the loop detected error
-	select {
-	case e := <-l.errCh:
-		if strings.Contains(e, "Loop detected") {
-			t.Fatalf("Loop detected: %v", e)
-		}
-	case <-time.After(250 * time.Millisecond):
-		// We are ok
-	}
-
-	// Now make sure we got the warning
-	select {
-	case w := <-l.warnCh:
-		if !strings.Contains(w, "Replacing connection from same server") {
-			t.Fatalf("Unexpected warning: %v", w)
-		}
-	case <-time.After(time.Second):
-		t.Fatal("Did not get expected warning")
-	}
-
-	checkLeafNodeConnected(t, s)
-	checkLeafNodeConnected(t, sl)
-}
diff --git a/server/monitor_test.go b/server/monitor_test.go
index 2a5555c0..0f78e9ae 100644
--- a/server/monitor_test.go
+++ b/server/monitor_test.go
@@ -3646,3 +3646,24 @@ func TestMonitorLeafz(t *testing.T) {
 		}
 	}
 }
+
+func TestMonitorAccountz(t *testing.T) {
+	s := RunServer(DefaultMonitorOptions())
+	defer s.Shutdown()
+	body := string(readBody(t, fmt.Sprintf("http://127.0.0.1:%d/accountz", s.MonitorAddr().Port)))
+	if !strings.Contains(body, `$G`) {
+		t.Fatalf("Body missing value. Contains: %s", body)
+	} else if !strings.Contains(body, `$SYS`) {
+		t.Fatalf("Body missing value. Contains: %s", body)
+	} else if !strings.Contains(body, `"accounts": [`) {
+		t.Fatalf("Body missing value. Contains: %s", body)
+	}
+	body = string(readBody(t, fmt.Sprintf("http://127.0.0.1:%d/accountz?acc=$SYS", s.MonitorAddr().Port)))
+	if !strings.Contains(body, `"account_detail": {`) {
+		t.Fatalf("Body missing value. Contains: %s", body)
+	} else if !strings.Contains(body, `"account_name": "$SYS",`) {
+		t.Fatalf("Body missing value. Contains: %s", body)
+	} else if !strings.Contains(body, `"subscriptions": 32,`) {
+		t.Fatalf("Body missing value. Contains: %s", body)
+	}
+}
diff --git a/test/leafnode_test.go b/test/leafnode_test.go
index eb142437..42314bf1 100644
--- a/test/leafnode_test.go
+++ b/test/leafnode_test.go
@@ -1722,7 +1722,7 @@ func TestLeafNodeExportsImports(t *testing.T) {
 	}
 }
 
-func TestLeafNodeExportImportComplexSetup(t *testing.T) {
+func TestLeadNodeExportImportComplexSetup(t *testing.T) {
 	content := `
 	port: -1
 	operator = "./configs/nkeys/op.jwt"