build(deps): bump github.com/nats-io/nats-server/v2
Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.11.9 to 2.12.0. - [Release notes](https://github.com/nats-io/nats-server/releases) - [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml) - [Commits](https://github.com/nats-io/nats-server/compare/v2.11.9...v2.12.0) --- updated-dependencies: - dependency-name: github.com/nats-io/nats-server/v2 dependency-version: 2.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
committed by
Ralf Haferkamp
parent
4a0cc1004f
commit
703b8dd084
@@ -56,7 +56,7 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/mna/pigeon v1.3.0
|
github.com/mna/pigeon v1.3.0
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||||
github.com/nats-io/nats-server/v2 v2.11.9
|
github.com/nats-io/nats-server/v2 v2.12.0
|
||||||
github.com/nats-io/nats.go v1.46.0
|
github.com/nats-io/nats.go v1.46.0
|
||||||
github.com/oklog/run v1.2.0
|
github.com/oklog/run v1.2.0
|
||||||
github.com/olekukonko/tablewriter v1.0.9
|
github.com/olekukonko/tablewriter v1.0.9
|
||||||
@@ -296,7 +296,7 @@ require (
|
|||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/mschoch/smat v0.2.0 // indirect
|
github.com/mschoch/smat v0.2.0 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/nats-io/jwt/v2 v2.7.4 // indirect
|
github.com/nats-io/jwt/v2 v2.8.0 // indirect
|
||||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/nxadm/tail v1.4.8 // indirect
|
github.com/nxadm/tail v1.4.8 // indirect
|
||||||
|
|||||||
@@ -881,10 +881,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
|
|||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
||||||
github.com/nats-io/jwt/v2 v2.7.4 h1:jXFuDDxs/GQjGDZGhNgH4tXzSUK6WQi2rsj4xmsNOtI=
|
github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g=
|
||||||
github.com/nats-io/jwt/v2 v2.7.4/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=
|
github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=
|
||||||
github.com/nats-io/nats-server/v2 v2.11.9 h1:k7nzHZjUf51W1b08xiQih63Rdxh0yr5O4K892Mx5gQA=
|
github.com/nats-io/nats-server/v2 v2.12.0 h1:OIwe8jZUqJFrh+hhiyKu8snNib66qsx806OslqJuo74=
|
||||||
github.com/nats-io/nats-server/v2 v2.11.9/go.mod h1:1MQgsAQX1tVjpf3Yzrk3x2pzdsZiNL/TVP3Amhp3CR8=
|
github.com/nats-io/nats-server/v2 v2.12.0/go.mod h1:nr8dhzqkP5E/lDwmn+A2CvQPMd1yDKXQI7iGg3lAvww=
|
||||||
github.com/nats-io/nats.go v1.46.0 h1:iUcX+MLT0HHXskGkz+Sg20sXrPtJLsOojMDTDzOHSb8=
|
github.com/nats-io/nats.go v1.46.0 h1:iUcX+MLT0HHXskGkz+Sg20sXrPtJLsOojMDTDzOHSb8=
|
||||||
github.com/nats-io/nats.go v1.46.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
github.com/nats-io/nats.go v1.46.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||||
|
|||||||
+1
@@ -36,6 +36,7 @@ type UserPermissionLimits struct {
|
|||||||
Permissions
|
Permissions
|
||||||
Limits
|
Limits
|
||||||
BearerToken bool `json:"bearer_token,omitempty"`
|
BearerToken bool `json:"bearer_token,omitempty"`
|
||||||
|
ProxyRequired bool `json:"proxy_required,omitempty"`
|
||||||
AllowedConnectionTypes StringList `json:"allowed_connection_types,omitempty"`
|
AllowedConnectionTypes StringList `json:"allowed_connection_types,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+171
-13
@@ -25,6 +25,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -65,6 +66,7 @@ type NkeyUser struct {
|
|||||||
Account *Account `json:"account,omitempty"`
|
Account *Account `json:"account,omitempty"`
|
||||||
SigningKey string `json:"signing_key,omitempty"`
|
SigningKey string `json:"signing_key,omitempty"`
|
||||||
AllowedConnectionTypes map[string]struct{} `json:"connection_types,omitempty"`
|
AllowedConnectionTypes map[string]struct{} `json:"connection_types,omitempty"`
|
||||||
|
ProxyRequired bool `json:"proxy_required,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// User is for multiple accounts/users.
|
// User is for multiple accounts/users.
|
||||||
@@ -75,6 +77,7 @@ type User struct {
|
|||||||
Account *Account `json:"account,omitempty"`
|
Account *Account `json:"account,omitempty"`
|
||||||
ConnectionDeadline time.Time `json:"connection_deadline,omitempty"`
|
ConnectionDeadline time.Time `json:"connection_deadline,omitempty"`
|
||||||
AllowedConnectionTypes map[string]struct{} `json:"connection_types,omitempty"`
|
AllowedConnectionTypes map[string]struct{} `json:"connection_types,omitempty"`
|
||||||
|
ProxyRequired bool `json:"proxy_required,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// clone performs a deep copy of the User struct, returning a new clone with
|
// clone performs a deep copy of the User struct, returning a new clone with
|
||||||
@@ -593,10 +596,30 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) (au
|
|||||||
ao bool // auth override
|
ao bool // auth override
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Little helper that will log the error as a debug statement, set the auth error in
|
||||||
|
// the connection and return false to indicate authentication failure.
|
||||||
|
setProxyAuthError := func(err error) bool {
|
||||||
|
c.Debugf(err.Error())
|
||||||
|
c.setAuthError(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicate if this connection came from a trusted proxy. Note that if
|
||||||
|
// trustedProxy could be false even if the connection is proxied, but it
|
||||||
|
// means that there was no trusted proxy configured.
|
||||||
|
trustedProxy, ok := s.proxyCheck(c, opts)
|
||||||
|
if trustedProxy && !ok {
|
||||||
|
return setProxyAuthError(ErrAuthProxyNotTrusted)
|
||||||
|
}
|
||||||
|
|
||||||
|
var proxyRequired bool
|
||||||
// Check if we have auth callouts enabled at the server level or in the bound account.
|
// Check if we have auth callouts enabled at the server level or in the bound account.
|
||||||
defer func() {
|
defer func() {
|
||||||
// Default reason
|
authErr := c.getAuthError()
|
||||||
reason := AuthenticationViolation.String()
|
if authErr == nil {
|
||||||
|
authErr = ErrAuthentication
|
||||||
|
}
|
||||||
|
reason := getAuthErrClosedState(authErr).String()
|
||||||
// No-op
|
// No-op
|
||||||
if juc == nil && opts.AuthCallout == nil {
|
if juc == nil && opts.AuthCallout == nil {
|
||||||
if !authorized {
|
if !authorized {
|
||||||
@@ -656,7 +679,7 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) (au
|
|||||||
// If we are here we have an auth callout defined and we have failed auth so far
|
// If we are here we have an auth callout defined and we have failed auth so far
|
||||||
// so we will callout to our auth backend for processing.
|
// so we will callout to our auth backend for processing.
|
||||||
if !skip {
|
if !skip {
|
||||||
authorized, reason = s.processClientOrLeafCallout(c, opts)
|
authorized, reason = s.processClientOrLeafCallout(c, opts, proxyRequired, trustedProxy)
|
||||||
}
|
}
|
||||||
// Check if we are authorized and in the auth callout account, and if so add in deny publish permissions for the auth subject.
|
// Check if we are authorized and in the auth callout account, and if so add in deny publish permissions for the auth subject.
|
||||||
if authorized {
|
if authorized {
|
||||||
@@ -697,6 +720,11 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) (au
|
|||||||
if !authRequired {
|
if !authRequired {
|
||||||
// TODO(dlc) - If they send us credentials should we fail?
|
// TODO(dlc) - If they send us credentials should we fail?
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
if c.kind == LEAF {
|
||||||
|
// Auth is not required, register the leaf node with the selected account.
|
||||||
|
// Otherwise, auth needs to match client auth.
|
||||||
|
return s.registerLeafWithAccount(c, opts.LeafNode.Account)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
@@ -769,6 +797,10 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) (au
|
|||||||
c.Debugf("User JWT not valid: %v", err)
|
c.Debugf("User JWT not valid: %v", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if proxyRequired = juc.ProxyRequired; proxyRequired && !trustedProxy {
|
||||||
|
s.mu.Unlock()
|
||||||
|
return setProxyAuthError(ErrAuthProxyRequired)
|
||||||
|
}
|
||||||
vr := jwt.CreateValidationResults()
|
vr := jwt.CreateValidationResults()
|
||||||
juc.Validate(vr)
|
juc.Validate(vr)
|
||||||
if vr.IsBlocking(true) {
|
if vr.IsBlocking(true) {
|
||||||
@@ -1045,6 +1077,9 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) (au
|
|||||||
}
|
}
|
||||||
|
|
||||||
if nkey != nil {
|
if nkey != nil {
|
||||||
|
if proxyRequired = nkey.ProxyRequired; proxyRequired && !trustedProxy {
|
||||||
|
return setProxyAuthError(ErrAuthProxyRequired)
|
||||||
|
}
|
||||||
// If we did not match noAuthUser check signature which is required.
|
// If we did not match noAuthUser check signature which is required.
|
||||||
if nkey.Nkey != noAuthUser {
|
if nkey.Nkey != noAuthUser {
|
||||||
if c.opts.Sig == _EMPTY_ {
|
if c.opts.Sig == _EMPTY_ {
|
||||||
@@ -1076,6 +1111,9 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) (au
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if user != nil {
|
if user != nil {
|
||||||
|
if proxyRequired = user.ProxyRequired; proxyRequired && !trustedProxy {
|
||||||
|
return setProxyAuthError(ErrAuthProxyRequired)
|
||||||
|
}
|
||||||
ok = comparePasswords(user.Password, c.opts.Password)
|
ok = comparePasswords(user.Password, c.opts.Password)
|
||||||
// If we are authorized, register the user which will properly setup any permissions
|
// If we are authorized, register the user which will properly setup any permissions
|
||||||
// for pub/sub authorizations.
|
// for pub/sub authorizations.
|
||||||
@@ -1086,6 +1124,9 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) (au
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.kind == CLIENT {
|
if c.kind == CLIENT {
|
||||||
|
if proxyRequired = opts.ProxyRequired; proxyRequired && !trustedProxy {
|
||||||
|
return setProxyAuthError(ErrAuthProxyRequired)
|
||||||
|
}
|
||||||
if token != _EMPTY_ {
|
if token != _EMPTY_ {
|
||||||
return comparePasswords(token, c.opts.Token)
|
return comparePasswords(token, c.opts.Token)
|
||||||
} else if username != _EMPTY_ {
|
} else if username != _EMPTY_ {
|
||||||
@@ -1094,16 +1135,65 @@ func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) (au
|
|||||||
}
|
}
|
||||||
return comparePasswords(password, c.opts.Password)
|
return comparePasswords(password, c.opts.Password)
|
||||||
}
|
}
|
||||||
} else if c.kind == LEAF {
|
|
||||||
// There is no required username/password to connect and
|
|
||||||
// there was no u/p in the CONNECT or none that matches the
|
|
||||||
// know users. Register the leaf connection with global account
|
|
||||||
// or the one specified in config (if provided).
|
|
||||||
return s.registerLeafWithAccount(c, opts.LeafNode.Account)
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there are configured trusted proxies and this connection comes
|
||||||
|
// from a proxy whose signature can be verified by one of the known
|
||||||
|
// trusted key, this function will return `true, true`. If the signature
|
||||||
|
// cannot be verified by any, it will return `true, false`.
|
||||||
|
// If the connectio is not proxied, or there are no configured trusted
|
||||||
|
// proxies, then this function returns `false, false`.
|
||||||
|
//
|
||||||
|
// Server lock MUST NOT be held on entry since this function will grab
|
||||||
|
// the read lock to extract the list of proxy trusted keys. The signature
|
||||||
|
// verification process will be done outside of the lock.
|
||||||
|
func (s *Server) proxyCheck(c *client, opts *Options) (bool, bool) {
|
||||||
|
// If there is no signature or no configured trusted proxy, return false.
|
||||||
|
psig := c.opts.ProxySig
|
||||||
|
if psig == _EMPTY_ || opts.Proxies == nil || len(opts.Proxies.Trusted) == 0 {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
// Decode the signature.
|
||||||
|
sig, err := base64.RawURLEncoding.DecodeString(psig)
|
||||||
|
if err != nil {
|
||||||
|
c.Debugf("Proxy signature not valid base64")
|
||||||
|
return true, false
|
||||||
|
}
|
||||||
|
// Go through the trusted keys and verify the signature.
|
||||||
|
s.mu.RLock()
|
||||||
|
keys := slices.Clone(s.proxiesKeyPairs)
|
||||||
|
s.mu.RUnlock()
|
||||||
|
for _, kp := range keys {
|
||||||
|
// We stop at the first that is valid.
|
||||||
|
if err := kp.Verify(c.nonce, sig); err == nil {
|
||||||
|
pub, _ := kp.PublicKey()
|
||||||
|
// Track which proxy public key is used by this connection.
|
||||||
|
c.mu.Lock()
|
||||||
|
c.proxyKey = pub
|
||||||
|
cid := c.cid
|
||||||
|
c.mu.Unlock()
|
||||||
|
// Track this proxied connection so that it can be closed
|
||||||
|
// if the trusted key is removed on configuration reload.
|
||||||
|
s.mu.Lock()
|
||||||
|
if s.proxiedConns == nil {
|
||||||
|
s.proxiedConns = make(map[string]map[uint64]*client)
|
||||||
|
}
|
||||||
|
clients := s.proxiedConns[pub]
|
||||||
|
if clients == nil {
|
||||||
|
clients = make(map[uint64]*client)
|
||||||
|
}
|
||||||
|
clients[cid] = c
|
||||||
|
s.proxiedConns[pub] = clients
|
||||||
|
s.mu.Unlock()
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We could not verify the signature, so indicate failure.
|
||||||
|
return true, false
|
||||||
|
}
|
||||||
|
|
||||||
func getTLSAuthDCs(rdns *pkix.RDNSequence) string {
|
func getTLSAuthDCs(rdns *pkix.RDNSequence) string {
|
||||||
dcOID := asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 25}
|
dcOID := asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 25}
|
||||||
dcs := []string{}
|
dcs := []string{}
|
||||||
@@ -1352,7 +1442,25 @@ func (s *Server) registerLeafWithAccount(c *client, account string) bool {
|
|||||||
func (s *Server) isLeafNodeAuthorized(c *client) bool {
|
func (s *Server) isLeafNodeAuthorized(c *client) bool {
|
||||||
opts := s.getOpts()
|
opts := s.getOpts()
|
||||||
|
|
||||||
isAuthorized := func(username, password, account string) bool {
|
setProxyAuthError := func(err error) bool {
|
||||||
|
c.Debugf(err.Error())
|
||||||
|
c.setAuthError(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
isAuthorized := func(username, password, account string, proxyRequired bool) bool {
|
||||||
|
trustedProxy, ok := s.proxyCheck(c, opts)
|
||||||
|
if trustedProxy && !ok {
|
||||||
|
return setProxyAuthError(ErrAuthProxyNotTrusted)
|
||||||
|
}
|
||||||
|
// A given user may not be required, but if the boolean is set at the
|
||||||
|
// authorization top-level, then override.
|
||||||
|
if !proxyRequired && opts.LeafNode.ProxyRequired {
|
||||||
|
proxyRequired = true
|
||||||
|
}
|
||||||
|
if proxyRequired && !trustedProxy {
|
||||||
|
return setProxyAuthError(ErrAuthProxyRequired)
|
||||||
|
}
|
||||||
if username != c.opts.Username {
|
if username != c.opts.Username {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -1366,8 +1474,16 @@ func (s *Server) isLeafNodeAuthorized(c *client) bool {
|
|||||||
// The user in CONNECT must match. We will bind to the account associated
|
// The user in CONNECT must match. We will bind to the account associated
|
||||||
// with that user (from the leafnode's authorization{} config).
|
// with that user (from the leafnode's authorization{} config).
|
||||||
if opts.LeafNode.Username != _EMPTY_ {
|
if opts.LeafNode.Username != _EMPTY_ {
|
||||||
return isAuthorized(opts.LeafNode.Username, opts.LeafNode.Password, opts.LeafNode.Account)
|
return isAuthorized(opts.LeafNode.Username, opts.LeafNode.Password, opts.LeafNode.Account,
|
||||||
|
opts.LeafNode.ProxyRequired)
|
||||||
} else if opts.LeafNode.Nkey != _EMPTY_ {
|
} else if opts.LeafNode.Nkey != _EMPTY_ {
|
||||||
|
trustedProxy, ok := s.proxyCheck(c, opts)
|
||||||
|
if trustedProxy && !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if opts.LeafNode.ProxyRequired && !trustedProxy {
|
||||||
|
return setProxyAuthError(ErrAuthProxyRequired)
|
||||||
|
}
|
||||||
if c.opts.Nkey != opts.LeafNode.Nkey {
|
if c.opts.Nkey != opts.LeafNode.Nkey {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -1421,7 +1537,7 @@ func (s *Server) isLeafNodeAuthorized(c *client) bool {
|
|||||||
}
|
}
|
||||||
// This will authorize since are using an existing user,
|
// This will authorize since are using an existing user,
|
||||||
// but it will also register with proper account.
|
// but it will also register with proper account.
|
||||||
return isAuthorized(user.Username, user.Password, accName)
|
return isAuthorized(user.Username, user.Password, accName, user.ProxyRequired)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is expected to be a very small array.
|
// This is expected to be a very small array.
|
||||||
@@ -1431,7 +1547,7 @@ func (s *Server) isLeafNodeAuthorized(c *client) bool {
|
|||||||
if u.Account != nil {
|
if u.Account != nil {
|
||||||
accName = u.Account.Name
|
accName = u.Account.Name
|
||||||
}
|
}
|
||||||
return isAuthorized(u.Username, u.Password, accName)
|
return isAuthorized(u.Username, u.Password, accName, u.ProxyRequired)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -1536,3 +1652,45 @@ func validateNoAuthUser(o *Options, noAuthUser string) error {
|
|||||||
`no_auth_user: "%s" not present as user or nkey in authorization block or account configuration`,
|
`no_auth_user: "%s" not present as user or nkey in authorization block or account configuration`,
|
||||||
noAuthUser)
|
noAuthUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateProxies(o *Options) error {
|
||||||
|
if o.Proxies == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, p := range o.Proxies.Trusted {
|
||||||
|
if !nkeys.IsValidPublicKey(p.Key) {
|
||||||
|
return fmt.Errorf("proxy trusted key %q is invalid", p.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a list of nkeys.KeyPair corresponding to the public keys
|
||||||
|
// of the Proxies.TrustedKeys list.
|
||||||
|
// Server lock must be held on entry.
|
||||||
|
func (s *Server) processProxiesTrustedKeys() {
|
||||||
|
// We could be here on reload.
|
||||||
|
if s.proxiesKeyPairs != nil {
|
||||||
|
s.proxiesKeyPairs = s.proxiesKeyPairs[:0]
|
||||||
|
}
|
||||||
|
if opts := s.getOpts(); opts.Proxies == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, p := range s.getOpts().Proxies.Trusted {
|
||||||
|
// Can't fail since we have already checked that it was a valid key.
|
||||||
|
kp, _ := nkeys.FromPublicKey(p.Key)
|
||||||
|
s.proxiesKeyPairs = append(s.proxiesKeyPairs, kp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the connection's `ClosedState` for the given authenication error.
|
||||||
|
func getAuthErrClosedState(authErr error) ClosedState {
|
||||||
|
switch authErr {
|
||||||
|
case ErrAuthProxyNotTrusted:
|
||||||
|
return ProxyNotTrusted
|
||||||
|
case ErrAuthProxyRequired:
|
||||||
|
return ProxyRequired
|
||||||
|
default:
|
||||||
|
return AuthenticationViolation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+12
-2
@@ -33,7 +33,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Process a callout on this client's behalf.
|
// Process a callout on this client's behalf.
|
||||||
func (s *Server) processClientOrLeafCallout(c *client, opts *Options) (authorized bool, errStr string) {
|
func (s *Server) processClientOrLeafCallout(c *client, opts *Options, proxyRequired, trustedProxy bool) (authorized bool, errStr string) {
|
||||||
isOperatorMode := len(opts.TrustedKeys) > 0
|
isOperatorMode := len(opts.TrustedKeys) > 0
|
||||||
|
|
||||||
// this is the account the user connected in, or the one running the callout
|
// this is the account the user connected in, or the one running the callout
|
||||||
@@ -245,6 +245,16 @@ func (s *Server) processClientOrLeafCallout(c *client, opts *Options) (authorize
|
|||||||
respCh <- titleCase(err.Error())
|
respCh <- titleCase(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// If the caller had established that the user should go through a proxy,
|
||||||
|
// or if the `arc` JWT requires it, and we don't have a trusted proxy,
|
||||||
|
// reject the connection.
|
||||||
|
if (proxyRequired || arc.ProxyRequired) && !trustedProxy {
|
||||||
|
err = ErrAuthProxyRequired
|
||||||
|
c.setAuthError(err)
|
||||||
|
c.authViolation()
|
||||||
|
respCh <- titleCase(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
vr := jwt.CreateValidationResults()
|
vr := jwt.CreateValidationResults()
|
||||||
arc.Validate(vr)
|
arc.Validate(vr)
|
||||||
if len(vr.Issues) > 0 {
|
if len(vr.Issues) > 0 {
|
||||||
@@ -369,7 +379,7 @@ func (s *Server) processClientOrLeafCallout(c *client, opts *Options) (authorize
|
|||||||
conn := c.nc.(*tls.Conn)
|
conn := c.nc.(*tls.Conn)
|
||||||
cs := conn.ConnectionState()
|
cs := conn.ConnectionState()
|
||||||
ct.Version = tlsVersion(cs.Version)
|
ct.Version = tlsVersion(cs.Version)
|
||||||
ct.Cipher = tlsCipher(cs.CipherSuite)
|
ct.Cipher = tls.CipherSuiteName(cs.CipherSuite)
|
||||||
// Check verified chains.
|
// Check verified chains.
|
||||||
for _, vs := range cs.VerifiedChains {
|
for _, vs := range cs.VerifiedChains {
|
||||||
var certs []string
|
var certs []string
|
||||||
|
|||||||
+23
-64
@@ -17,85 +17,44 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Where we maintain all of the available ciphers
|
func init() {
|
||||||
var cipherMap = map[string]uint16{
|
for _, cs := range tls.CipherSuites() {
|
||||||
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
|
cipherMap[cs.Name] = cs
|
||||||
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
cipherMapByID[cs.ID] = cs
|
||||||
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
}
|
||||||
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
for _, cs := range tls.InsecureCipherSuites() {
|
||||||
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
cipherMap[cs.Name] = cs
|
||||||
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
cipherMapByID[cs.ID] = cs
|
||||||
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
}
|
||||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
|
||||||
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
|
||||||
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
|
||||||
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
|
||||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
|
||||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
|
||||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
|
||||||
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
|
||||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
||||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
||||||
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
||||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
||||||
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
|
||||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
|
||||||
"TLS_AES_128_GCM_SHA256": tls.TLS_AES_128_GCM_SHA256,
|
|
||||||
"TLS_AES_256_GCM_SHA384": tls.TLS_AES_256_GCM_SHA384,
|
|
||||||
"TLS_CHACHA20_POLY1305_SHA256": tls.TLS_CHACHA20_POLY1305_SHA256,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var cipherMapByID = map[uint16]string{
|
var cipherMap = map[string]*tls.CipherSuite{}
|
||||||
tls.TLS_RSA_WITH_RC4_128_SHA: "TLS_RSA_WITH_RC4_128_SHA",
|
var cipherMapByID = map[uint16]*tls.CipherSuite{}
|
||||||
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
|
|
||||||
tls.TLS_RSA_WITH_AES_128_CBC_SHA: "TLS_RSA_WITH_AES_128_CBC_SHA",
|
|
||||||
tls.TLS_RSA_WITH_AES_128_CBC_SHA256: "TLS_RSA_WITH_AES_128_CBC_SHA256",
|
|
||||||
tls.TLS_RSA_WITH_AES_256_CBC_SHA: "TLS_RSA_WITH_AES_256_CBC_SHA",
|
|
||||||
tls.TLS_RSA_WITH_AES_256_GCM_SHA384: "TLS_RSA_WITH_AES_256_GCM_SHA384",
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
|
||||||
tls.TLS_AES_128_GCM_SHA256: "TLS_AES_128_GCM_SHA256",
|
|
||||||
tls.TLS_AES_256_GCM_SHA384: "TLS_AES_256_GCM_SHA384",
|
|
||||||
tls.TLS_CHACHA20_POLY1305_SHA256: "TLS_CHACHA20_POLY1305_SHA256",
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultCipherSuites() []uint16 {
|
func defaultCipherSuites() []uint16 {
|
||||||
return []uint16{
|
ciphers := tls.CipherSuites()
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
defaults := make([]uint16, 0, len(ciphers))
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
for _, cs := range ciphers {
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
defaults = append(defaults, cs.ID)
|
||||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
||||||
}
|
}
|
||||||
|
return defaults
|
||||||
}
|
}
|
||||||
|
|
||||||
// Where we maintain available curve preferences
|
// Where we maintain available curve preferences
|
||||||
var curvePreferenceMap = map[string]tls.CurveID{
|
var curvePreferenceMap = map[string]tls.CurveID{
|
||||||
"X25519": tls.X25519,
|
"X25519MLKEM768": tls.X25519MLKEM768,
|
||||||
"CurveP256": tls.CurveP256,
|
"X25519": tls.X25519,
|
||||||
"CurveP384": tls.CurveP384,
|
"CurveP256": tls.CurveP256,
|
||||||
"CurveP521": tls.CurveP521,
|
"CurveP384": tls.CurveP384,
|
||||||
|
"CurveP521": tls.CurveP521,
|
||||||
}
|
}
|
||||||
|
|
||||||
// reorder to default to the highest level of security. See:
|
// reorder to default to the highest level of security. See:
|
||||||
// https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
|
// https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go
|
||||||
func defaultCurvePreferences() []tls.CurveID {
|
func defaultCurvePreferences() []tls.CurveID {
|
||||||
return []tls.CurveID{
|
return []tls.CurveID{
|
||||||
tls.X25519, // faster than P256, arguably more secure
|
tls.X25519MLKEM768, // post-quantum
|
||||||
|
tls.X25519, // faster than P256, arguably more secure
|
||||||
tls.CurveP256,
|
tls.CurveP256,
|
||||||
tls.CurveP384,
|
tls.CurveP384,
|
||||||
tls.CurveP521,
|
tls.CurveP521,
|
||||||
|
|||||||
+322
-130
@@ -35,6 +35,8 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/klauspost/compress/s2"
|
"github.com/klauspost/compress/s2"
|
||||||
"github.com/nats-io/jwt/v2"
|
"github.com/nats-io/jwt/v2"
|
||||||
"github.com/nats-io/nats-server/v2/internal/fastrand"
|
"github.com/nats-io/nats-server/v2/internal/fastrand"
|
||||||
@@ -222,6 +224,8 @@ const (
|
|||||||
MinimumVersionRequired
|
MinimumVersionRequired
|
||||||
ClusterNamesIdentical
|
ClusterNamesIdentical
|
||||||
Kicked
|
Kicked
|
||||||
|
ProxyNotTrusted
|
||||||
|
ProxyRequired
|
||||||
)
|
)
|
||||||
|
|
||||||
// Some flags passed to processMsgResults
|
// Some flags passed to processMsgResults
|
||||||
@@ -255,6 +259,8 @@ type client struct {
|
|||||||
pubKey string
|
pubKey string
|
||||||
nc net.Conn
|
nc net.Conn
|
||||||
ncs atomic.Value
|
ncs atomic.Value
|
||||||
|
ncsAcc atomic.Value
|
||||||
|
ncsUser atomic.Value
|
||||||
out outbound
|
out outbound
|
||||||
user *NkeyUser
|
user *NkeyUser
|
||||||
host string
|
host string
|
||||||
@@ -270,6 +276,7 @@ type client struct {
|
|||||||
msgb [msgScratchSize]byte
|
msgb [msgScratchSize]byte
|
||||||
last time.Time
|
last time.Time
|
||||||
lastIn time.Time
|
lastIn time.Time
|
||||||
|
proxyKey string
|
||||||
|
|
||||||
repliesSincePrune uint16
|
repliesSincePrune uint16
|
||||||
lastReplyPrune time.Time
|
lastReplyPrune time.Time
|
||||||
@@ -298,6 +305,13 @@ type client struct {
|
|||||||
nameTag string
|
nameTag string
|
||||||
|
|
||||||
tlsTo *time.Timer
|
tlsTo *time.Timer
|
||||||
|
|
||||||
|
// Authentication error override. This is used because the authentication
|
||||||
|
// stack is simply returning a boolean, and the only authentication error
|
||||||
|
// reported is the generic `ErrAuthentication`. In the authentication code,
|
||||||
|
// if we want to report a different error, we can now set this field
|
||||||
|
// and `authViolation()` will use that one.
|
||||||
|
authErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
type rrTracking struct {
|
type rrTracking struct {
|
||||||
@@ -494,15 +508,13 @@ func (rcf readCacheFlag) isSet(c readCacheFlag) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultMaxPerAccountCacheSize = 8192
|
defaultMaxPerAccountCacheSize = 8192
|
||||||
defaultPrunePerAccountCacheSize = 1024
|
defaultClosedSubsCheckInterval = 5 * time.Minute
|
||||||
defaultClosedSubsCheckInterval = 5 * time.Minute
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
maxPerAccountCacheSize = defaultMaxPerAccountCacheSize
|
maxPerAccountCacheSize = defaultMaxPerAccountCacheSize
|
||||||
prunePerAccountCacheSize = defaultPrunePerAccountCacheSize
|
closedSubsCheckInterval = defaultClosedSubsCheckInterval
|
||||||
closedSubsCheckInterval = defaultClosedSubsCheckInterval
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// perAccountCache is for L1 semantics for inbound messages from a route or gateway to mimic the performance of clients.
|
// perAccountCache is for L1 semantics for inbound messages from a route or gateway to mimic the performance of clients.
|
||||||
@@ -652,6 +664,9 @@ type ClientOpts struct {
|
|||||||
|
|
||||||
// Leafnodes
|
// Leafnodes
|
||||||
RemoteAccount string `json:"remote_account,omitempty"`
|
RemoteAccount string `json:"remote_account,omitempty"`
|
||||||
|
|
||||||
|
// Proxy would include its own nonce signature.
|
||||||
|
ProxySig string `json:"proxy_sig,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultOpts = ClientOpts{Verbose: true, Pedantic: true, Echo: true}
|
var defaultOpts = ClientOpts{Verbose: true, Pedantic: true, Echo: true}
|
||||||
@@ -1854,6 +1869,19 @@ func (c *client) markConnAsClosed(reason ClosedState) {
|
|||||||
case ReadError, WriteError, SlowConsumerPendingBytes, SlowConsumerWriteDeadline, TLSHandshakeError:
|
case ReadError, WriteError, SlowConsumerPendingBytes, SlowConsumerWriteDeadline, TLSHandshakeError:
|
||||||
c.flags.set(skipFlushOnClose)
|
c.flags.set(skipFlushOnClose)
|
||||||
skipFlush = true
|
skipFlush = true
|
||||||
|
case StaleConnection:
|
||||||
|
// Track stale connections statistics.
|
||||||
|
atomic.AddInt64(&c.srv.staleConnections, 1)
|
||||||
|
switch c.kind {
|
||||||
|
case CLIENT:
|
||||||
|
c.srv.staleStats.clients.Add(1)
|
||||||
|
case ROUTER:
|
||||||
|
c.srv.staleStats.routes.Add(1)
|
||||||
|
case GATEWAY:
|
||||||
|
c.srv.staleStats.gateways.Add(1)
|
||||||
|
case LEAF:
|
||||||
|
c.srv.staleStats.leafs.Add(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if c.flags.isSet(connMarkedClosed) {
|
if c.flags.isSet(connMarkedClosed) {
|
||||||
return
|
return
|
||||||
@@ -1867,14 +1895,25 @@ func (c *client) markConnAsClosed(reason ClosedState) {
|
|||||||
// Be consistent with the creation: for routes, gateways and leaf,
|
// Be consistent with the creation: for routes, gateways and leaf,
|
||||||
// we use Noticef on create, so use that too for delete.
|
// we use Noticef on create, so use that too for delete.
|
||||||
if c.srv != nil {
|
if c.srv != nil {
|
||||||
if c.kind == LEAF {
|
if c.kind == LEAF || c.kind == ROUTER || c.kind == GATEWAY {
|
||||||
if c.acc != nil {
|
var tags []string
|
||||||
c.Noticef("%s connection closed: %s - Account: %s", c.kindString(), reason, c.acc.traceLabel())
|
var remoteName string
|
||||||
|
switch {
|
||||||
|
case c.kind == LEAF && c.leaf != nil:
|
||||||
|
remoteName = c.leaf.remoteServer
|
||||||
|
case c.kind == ROUTER && c.route != nil:
|
||||||
|
remoteName = c.route.remoteName
|
||||||
|
case c.kind == GATEWAY && c.gw != nil:
|
||||||
|
remoteName = c.gw.remoteName
|
||||||
|
}
|
||||||
|
if remoteName != _EMPTY_ {
|
||||||
|
tags = append(tags, fmt.Sprintf("Remote: %s", remoteName))
|
||||||
|
}
|
||||||
|
if len(tags) > 0 {
|
||||||
|
c.Noticef("%s connection closed: %s - %s", c.kindString(), reason, strings.Join(tags, ", "))
|
||||||
} else {
|
} else {
|
||||||
c.Noticef("%s connection closed: %s", c.kindString(), reason)
|
c.Noticef("%s connection closed: %s", c.kindString(), reason)
|
||||||
}
|
}
|
||||||
} else if c.kind == ROUTER || c.kind == GATEWAY {
|
|
||||||
c.Noticef("%s connection closed: %s", c.kindString(), reason)
|
|
||||||
} else { // Client, System, Jetstream, and Account connections.
|
} else { // Client, System, Jetstream, and Account connections.
|
||||||
c.Debugf("%s connection closed: %s", c.kindString(), reason)
|
c.Debugf("%s connection closed: %s", c.kindString(), reason)
|
||||||
}
|
}
|
||||||
@@ -1917,7 +1956,7 @@ func (c *client) flushSignal() {
|
|||||||
|
|
||||||
// Traces a message.
|
// Traces a message.
|
||||||
// Will NOT check if tracing is enabled, does NOT need the client lock.
|
// Will NOT check if tracing is enabled, does NOT need the client lock.
|
||||||
func (c *client) traceMsg(msg []byte) {
|
func (c *client) traceMsgInternal(msg []byte, delivered bool, hdrSize int) {
|
||||||
opts := c.srv.getOpts()
|
opts := c.srv.getOpts()
|
||||||
maxTrace := opts.MaxTracedMsgLen
|
maxTrace := opts.MaxTracedMsgLen
|
||||||
headersOnly := opts.TraceHeaders
|
headersOnly := opts.TraceHeaders
|
||||||
@@ -1926,8 +1965,13 @@ func (c *client) traceMsg(msg []byte) {
|
|||||||
// If TraceHeaders is enabled, extract only the header portion of the msg.
|
// If TraceHeaders is enabled, extract only the header portion of the msg.
|
||||||
// If a header is present, it ends with an additional trailing CRLF.
|
// If a header is present, it ends with an additional trailing CRLF.
|
||||||
if headersOnly {
|
if headersOnly {
|
||||||
msg, _ = c.msgParts(msg)
|
if hdrSize > 0 && len(msg) >= hdrSize {
|
||||||
suffix += LEN_CR_LF
|
msg = msg[:hdrSize]
|
||||||
|
suffix += LEN_CR_LF
|
||||||
|
} else {
|
||||||
|
// No headers present, so nothing to trace.
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not emit a log line for zero-length payloads.
|
// Do not emit a log line for zero-length payloads.
|
||||||
@@ -1936,14 +1980,32 @@ func (c *client) traceMsg(msg []byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
traceInPrefix string = "<<-"
|
||||||
|
traceOutPrefix string = "->>"
|
||||||
|
)
|
||||||
|
var prefix string
|
||||||
|
if delivered {
|
||||||
|
prefix = traceOutPrefix
|
||||||
|
} else {
|
||||||
|
prefix = traceInPrefix
|
||||||
|
}
|
||||||
if maxTrace > 0 && l > maxTrace {
|
if maxTrace > 0 && l > maxTrace {
|
||||||
tm := fmt.Sprintf("%q", msg[:maxTrace])
|
tm := fmt.Sprintf("%q", msg[:maxTrace])
|
||||||
c.Tracef("<<- MSG_PAYLOAD: [\"%s...\"]", tm[1:len(tm)-1])
|
c.Tracef("%s MSG_PAYLOAD: [\"%s...\"]", prefix, tm[1:len(tm)-1])
|
||||||
} else {
|
} else {
|
||||||
c.Tracef("<<- MSG_PAYLOAD: [%q]", msg[:l])
|
c.Tracef("%s MSG_PAYLOAD: [%q]", prefix, msg[:l])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *client) traceMsg(msg []byte) {
|
||||||
|
c.traceMsgInternal(msg, false, c.pa.hdr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) traceMsgDelivery(msg []byte, hdrSize int) {
|
||||||
|
c.traceMsgInternal(msg, true, hdrSize)
|
||||||
|
}
|
||||||
|
|
||||||
// Traces an incoming operation.
|
// Traces an incoming operation.
|
||||||
// Will NOT check if tracing is enabled, does NOT need the client lock.
|
// Will NOT check if tracing is enabled, does NOT need the client lock.
|
||||||
func (c *client) traceInOp(op string, arg []byte) {
|
func (c *client) traceInOp(op string, arg []byte) {
|
||||||
@@ -2094,6 +2156,7 @@ func (c *client) processConnect(arg []byte) error {
|
|||||||
}
|
}
|
||||||
// Indicate that the CONNECT protocol has been received, and that the
|
// Indicate that the CONNECT protocol has been received, and that the
|
||||||
// server now knows which protocol this client supports.
|
// server now knows which protocol this client supports.
|
||||||
|
firstConnect := !c.flags.isSet(connectReceived)
|
||||||
c.flags.set(connectReceived)
|
c.flags.set(connectReceived)
|
||||||
// Capture these under lock
|
// Capture these under lock
|
||||||
c.echo = c.opts.Echo
|
c.echo = c.opts.Echo
|
||||||
@@ -2103,30 +2166,6 @@ func (c *client) processConnect(arg []byte) error {
|
|||||||
account := c.opts.Account
|
account := c.opts.Account
|
||||||
accountNew := c.opts.AccountNew
|
accountNew := c.opts.AccountNew
|
||||||
|
|
||||||
if c.kind == CLIENT {
|
|
||||||
var ncs string
|
|
||||||
if c.opts.Version != _EMPTY_ {
|
|
||||||
ncs = fmt.Sprintf("v%s", c.opts.Version)
|
|
||||||
}
|
|
||||||
if c.opts.Lang != _EMPTY_ {
|
|
||||||
if c.opts.Version == _EMPTY_ {
|
|
||||||
ncs = c.opts.Lang
|
|
||||||
} else {
|
|
||||||
ncs = fmt.Sprintf("%s:%s", ncs, c.opts.Lang)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.opts.Name != _EMPTY_ {
|
|
||||||
if c.opts.Version == _EMPTY_ && c.opts.Lang == _EMPTY_ {
|
|
||||||
ncs = c.opts.Name
|
|
||||||
} else {
|
|
||||||
ncs = fmt.Sprintf("%s:%s", ncs, c.opts.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ncs != _EMPTY_ {
|
|
||||||
c.ncs.CompareAndSwap(nil, fmt.Sprintf("%s - %q", c, ncs))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if websocket client, maybe some options through cookies
|
// if websocket client, maybe some options through cookies
|
||||||
if ws := c.ws; ws != nil {
|
if ws := c.ws; ws != nil {
|
||||||
// if JWT not in the CONNECT, use the cookie JWT (possibly empty).
|
// if JWT not in the CONNECT, use the cookie JWT (possibly empty).
|
||||||
@@ -2200,6 +2239,55 @@ func (c *client) processConnect(arg []byte) error {
|
|||||||
// By default register with the global account.
|
// By default register with the global account.
|
||||||
c.registerWithAccount(srv.globalAccount())
|
c.registerWithAccount(srv.globalAccount())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize user info used in logs.
|
||||||
|
c.mu.Lock()
|
||||||
|
acc := c.acc
|
||||||
|
if c.getRawAuthUser() != _EMPTY_ {
|
||||||
|
c.ncsUser.Store(c.getAuthUserLabel())
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
if acc != nil {
|
||||||
|
acc.mu.RLock()
|
||||||
|
c.ncsAcc.Store(acc.traceLabel())
|
||||||
|
acc.mu.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable logging connection details and auth info for this client.
|
||||||
|
if c.kind == CLIENT && firstConnect && c.srv != nil {
|
||||||
|
var ncs string
|
||||||
|
if c.opts.Version != _EMPTY_ {
|
||||||
|
ncs = fmt.Sprintf("v%s", c.opts.Version)
|
||||||
|
}
|
||||||
|
if c.opts.Lang != _EMPTY_ {
|
||||||
|
if c.opts.Version == _EMPTY_ {
|
||||||
|
ncs = c.opts.Lang
|
||||||
|
} else {
|
||||||
|
ncs = fmt.Sprintf("%s:%s", ncs, c.opts.Lang)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.opts.Name != _EMPTY_ {
|
||||||
|
if c.opts.Version == _EMPTY_ && c.opts.Lang == _EMPTY_ {
|
||||||
|
ncs = c.opts.Name
|
||||||
|
} else {
|
||||||
|
ncs = fmt.Sprintf("%s:%s", ncs, c.opts.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var acs string
|
||||||
|
accl := c.ncsAcc.Load()
|
||||||
|
authUser := c.ncsUser.Load()
|
||||||
|
if accl != nil && authUser != nil {
|
||||||
|
acs = fmt.Sprintf("%s/%s", accl, authUser)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case ncs != _EMPTY_ && acs != _EMPTY_:
|
||||||
|
c.ncs.Store(fmt.Sprintf("%s - %q - %q", c, ncs, acs))
|
||||||
|
case ncs != _EMPTY_:
|
||||||
|
c.ncs.Store(fmt.Sprintf("%s - %q", c, ncs))
|
||||||
|
case acs != _EMPTY_:
|
||||||
|
c.ncs.Store(fmt.Sprintf("%s - %q", c, acs))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch kind {
|
switch kind {
|
||||||
@@ -2238,12 +2326,12 @@ func (c *client) processConnect(arg []byte) error {
|
|||||||
|
|
||||||
func (c *client) sendErrAndErr(err string) {
|
func (c *client) sendErrAndErr(err string) {
|
||||||
c.sendErr(err)
|
c.sendErr(err)
|
||||||
c.Errorf(err)
|
c.RateLimitErrorf(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) sendErrAndDebug(err string) {
|
func (c *client) sendErrAndDebug(err string) {
|
||||||
c.sendErr(err)
|
c.sendErr(err)
|
||||||
c.Debugf(err)
|
c.RateLimitDebugf(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) authTimeout() {
|
func (c *client) authTimeout() {
|
||||||
@@ -2262,38 +2350,28 @@ func (c *client) accountAuthExpired() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) authViolation() {
|
func (c *client) authViolation() {
|
||||||
var s *Server
|
authErr := c.getAuthError()
|
||||||
var hasTrustedNkeys, hasNkeys, hasUsers bool
|
if authErr == nil {
|
||||||
if s = c.srv; s != nil {
|
authErr = ErrAuthentication
|
||||||
s.mu.RLock()
|
|
||||||
hasTrustedNkeys = s.trustedKeys != nil
|
|
||||||
hasNkeys = s.nkeys != nil
|
|
||||||
hasUsers = s.users != nil
|
|
||||||
s.mu.RUnlock()
|
|
||||||
defer s.sendAuthErrorEvent(c)
|
|
||||||
}
|
}
|
||||||
|
reason := getAuthErrClosedState(authErr)
|
||||||
|
|
||||||
if hasTrustedNkeys {
|
var s *Server
|
||||||
c.Errorf("%v", ErrAuthentication)
|
if s = c.srv; s != nil {
|
||||||
} else if hasNkeys {
|
defer s.sendAuthErrorEvent(c, reason.String())
|
||||||
c.Errorf("%s - Nkey %q",
|
if c.getRawAuthUser() != _EMPTY_ {
|
||||||
ErrAuthentication.Error(),
|
c.Errorf("%v - %s", ErrAuthentication, c.getAuthUser())
|
||||||
c.opts.Nkey)
|
} else {
|
||||||
} else if hasUsers {
|
|
||||||
c.Errorf("%s - User %q",
|
|
||||||
ErrAuthentication.Error(),
|
|
||||||
c.opts.Username)
|
|
||||||
} else {
|
|
||||||
if c.srv != nil {
|
|
||||||
c.Errorf(ErrAuthentication.Error())
|
c.Errorf(ErrAuthentication.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.isMqtt() {
|
if c.isMqtt() {
|
||||||
c.mqttEnqueueConnAck(mqttConnAckRCNotAuthorized, false)
|
c.mqttEnqueueConnAck(mqttConnAckRCNotAuthorized, false)
|
||||||
} else {
|
} else {
|
||||||
|
// Send this to client, regardless of the authErr override.
|
||||||
c.sendErr("Authorization Violation")
|
c.sendErr("Authorization Violation")
|
||||||
}
|
}
|
||||||
c.closeConnection(AuthenticationViolation)
|
c.closeConnection(reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) maxAccountConnExceeded() {
|
func (c *client) maxAccountConnExceeded() {
|
||||||
@@ -2523,36 +2601,29 @@ func (c *client) processPing() {
|
|||||||
// If we are here, the CONNECT has been received so we know
|
// If we are here, the CONNECT has been received so we know
|
||||||
// if this client supports async INFO or not.
|
// if this client supports async INFO or not.
|
||||||
var (
|
var (
|
||||||
checkInfoChange bool
|
sendConnectInfo bool
|
||||||
srv = c.srv
|
srv = c.srv
|
||||||
)
|
)
|
||||||
// For older clients, just flip the firstPongSent flag if not already
|
// For the first PING (so firstPongSet is false) and for clients
|
||||||
// set and we are done.
|
// that support async INFO protocols, we will send one with ConnectInfo=true,
|
||||||
if c.opts.Protocol < ClientProtoInfo || srv == nil {
|
// the name of the account the client is bound to, and if the
|
||||||
c.flags.setIfNotSet(firstPongSent)
|
// account is the system account.
|
||||||
} else {
|
if !c.flags.isSet(firstPongSent) {
|
||||||
// This is a client that supports async INFO protocols.
|
// Flip the flag.
|
||||||
// If this is the first PING (so firstPongSent is not set yet),
|
c.flags.set(firstPongSent)
|
||||||
// we will need to check if there was a change in cluster topology
|
// Evaluate if we should send the INFO protocol.
|
||||||
// or we have a different max payload. We will send this first before
|
sendConnectInfo = srv != nil && c.opts.Protocol >= ClientProtoInfo
|
||||||
// pong since most clients do flush after connect call.
|
|
||||||
checkInfoChange = !c.flags.isSet(firstPongSent)
|
|
||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
if checkInfoChange {
|
if sendConnectInfo {
|
||||||
opts := srv.getOpts()
|
|
||||||
srv.mu.Lock()
|
srv.mu.Lock()
|
||||||
|
info := srv.copyInfo()
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
// Now that we are under both locks, we can flip the flag.
|
info.RemoteAccount = c.acc.Name
|
||||||
// This prevents sendAsyncInfoToClients() and code here to
|
info.IsSystemAccount = c.acc == srv.SystemAccount()
|
||||||
// send a double INFO protocol.
|
info.ConnectInfo = true
|
||||||
c.flags.set(firstPongSent)
|
c.enqueueProto(c.generateClientInfoJSON(info))
|
||||||
// If there was a cluster update since this client was created,
|
|
||||||
// send an updated INFO protocol now.
|
|
||||||
if srv.lastCURLsUpdate >= c.start.UnixNano() || c.mpay != int32(opts.MaxPayload) {
|
|
||||||
c.enqueueProto(c.generateClientInfoJSON(srv.copyInfo()))
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
srv.mu.Unlock()
|
srv.mu.Unlock()
|
||||||
}
|
}
|
||||||
@@ -3469,6 +3540,12 @@ func (c *client) stalledWait(producer *client) {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
defer c.mu.Lock()
|
defer c.mu.Lock()
|
||||||
|
|
||||||
|
// Track per client and total client stalls.
|
||||||
|
atomic.AddInt64(&c.stalls, 1)
|
||||||
|
if c.srv != nil {
|
||||||
|
atomic.AddInt64(&c.srv.stalls, 1)
|
||||||
|
}
|
||||||
|
|
||||||
// Now check if we are close to total allowed.
|
// Now check if we are close to total allowed.
|
||||||
if producer.in.tst+ttl > stallTotalAllowed {
|
if producer.in.tst+ttl > stallTotalAllowed {
|
||||||
ttl = stallTotalAllowed - producer.in.tst
|
ttl = stallTotalAllowed - producer.in.tst
|
||||||
@@ -3621,6 +3698,7 @@ func (c *client) deliverMsg(prodIsMQTT bool, sub *subscription, acc *Account, su
|
|||||||
// support we need to strip the headers from the payload.
|
// support we need to strip the headers from the payload.
|
||||||
// The actual header would have been processed correctly for us, so just
|
// The actual header would have been processed correctly for us, so just
|
||||||
// need to update payload.
|
// need to update payload.
|
||||||
|
hdrSize := c.pa.hdr
|
||||||
if c.pa.hdr > 0 && !sub.client.headers {
|
if c.pa.hdr > 0 && !sub.client.headers {
|
||||||
msg = msg[c.pa.hdr:]
|
msg = msg[c.pa.hdr:]
|
||||||
}
|
}
|
||||||
@@ -3762,6 +3840,7 @@ func (c *client) deliverMsg(prodIsMQTT bool, sub *subscription, acc *Account, su
|
|||||||
|
|
||||||
if client.trace {
|
if client.trace {
|
||||||
client.traceOutOp(bytesToString(mh[:len(mh)-LEN_CR_LF]), nil)
|
client.traceOutOp(bytesToString(mh[:len(mh)-LEN_CR_LF]), nil)
|
||||||
|
client.traceMsgDelivery(msg, hdrSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
client.mu.Unlock()
|
client.mu.Unlock()
|
||||||
@@ -4197,7 +4276,8 @@ func (c *client) processInboundClientMsg(msg []byte) (bool, bool) {
|
|||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
if c.opts.NoResponders {
|
if c.opts.NoResponders {
|
||||||
if sub := c.subForReply(c.pa.reply); sub != nil {
|
if sub := c.subForReply(c.pa.reply); sub != nil {
|
||||||
proto := fmt.Sprintf("HMSG %s %s 16 16\r\nNATS/1.0 503\r\n\r\n\r\n", c.pa.reply, sub.sid)
|
hdrLen := 32 /* header without the subject */ + len(c.pa.subject)
|
||||||
|
proto := fmt.Sprintf("HMSG %s %s %d %d\r\nNATS/1.0 503\r\nNats-Subject: %s\r\n\r\n\r\n", c.pa.reply, sub.sid, hdrLen, hdrLen, c.pa.subject)
|
||||||
c.queueOutbound([]byte(proto))
|
c.queueOutbound([]byte(proto))
|
||||||
c.addToPCD(c)
|
c.addToPCD(c)
|
||||||
}
|
}
|
||||||
@@ -4257,7 +4337,7 @@ func (c *client) setupResponseServiceImport(acc *Account, si *serviceImport, tra
|
|||||||
|
|
||||||
// Will remove a header if present.
|
// Will remove a header if present.
|
||||||
func removeHeaderIfPresent(hdr []byte, key string) []byte {
|
func removeHeaderIfPresent(hdr []byte, key string) []byte {
|
||||||
start := bytes.Index(hdr, []byte(key))
|
start := bytes.Index(hdr, []byte(key+":"))
|
||||||
// key can't be first and we want to check that it is preceded by a '\n'
|
// key can't be first and we want to check that it is preceded by a '\n'
|
||||||
if start < 1 || hdr[start-1] != '\n' {
|
if start < 1 || hdr[start-1] != '\n' {
|
||||||
return hdr
|
return hdr
|
||||||
@@ -4406,6 +4486,27 @@ func sliceHeader(key string, hdr []byte) []byte {
|
|||||||
return hdr[start:index:index]
|
return hdr[start:index:index]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setHeader(key, val string, hdr []byte) []byte {
|
||||||
|
prefix := []byte(key + ": ")
|
||||||
|
start := bytes.Index(hdr, prefix)
|
||||||
|
if start >= 0 {
|
||||||
|
valStart := start + len(prefix)
|
||||||
|
valEnd := bytes.Index(hdr[valStart:], []byte("\r"))
|
||||||
|
if valEnd < 0 {
|
||||||
|
return hdr // malformed headers
|
||||||
|
}
|
||||||
|
valEnd += valStart
|
||||||
|
suffix := slices.Clone(hdr[valEnd:])
|
||||||
|
newHdr := append(hdr[:valStart], val...)
|
||||||
|
return append(newHdr, suffix...)
|
||||||
|
}
|
||||||
|
if len(hdr) > 0 && bytes.HasSuffix(hdr, []byte("\r\n")) {
|
||||||
|
hdr = hdr[:len(hdr)-2]
|
||||||
|
val += "\r\n"
|
||||||
|
}
|
||||||
|
return fmt.Appendf(hdr, "%s: %s\r\n", key, val)
|
||||||
|
}
|
||||||
|
|
||||||
// For bytes.HasPrefix below.
|
// For bytes.HasPrefix below.
|
||||||
var (
|
var (
|
||||||
jsRequestNextPreB = []byte(jsRequestNextPre)
|
jsRequestNextPreB = []byte(jsRequestNextPre)
|
||||||
@@ -5272,18 +5373,16 @@ func (c *client) pubPermissionViolation(subject []byte) {
|
|||||||
mt.setIngressError(errTxt)
|
mt.setIngressError(errTxt)
|
||||||
}
|
}
|
||||||
c.sendErr(errTxt)
|
c.sendErr(errTxt)
|
||||||
c.Errorf("Publish Violation - %s, Subject %q", c.getAuthUser(), subject)
|
c.Errorf("Publish Violation - Subject %q", subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) subPermissionViolation(sub *subscription) {
|
func (c *client) subPermissionViolation(sub *subscription) {
|
||||||
errTxt := fmt.Sprintf("Permissions Violation for Subscription to %q", sub.subject)
|
errTxt := fmt.Sprintf("Permissions Violation for Subscription to %q", sub.subject)
|
||||||
logTxt := fmt.Sprintf("Subscription Violation - %s, Subject %q, SID %s",
|
logTxt := fmt.Sprintf("Subscription Violation - Subject %q, SID %s", sub.subject, sub.sid)
|
||||||
c.getAuthUser(), sub.subject, sub.sid)
|
|
||||||
|
|
||||||
if sub.queue != nil {
|
if sub.queue != nil {
|
||||||
errTxt = fmt.Sprintf("Permissions Violation for Subscription to %q using queue %q", sub.subject, sub.queue)
|
errTxt = fmt.Sprintf("Permissions Violation for Subscription to %q using queue %q", sub.subject, sub.queue)
|
||||||
logTxt = fmt.Sprintf("Subscription Violation - %s, Subject %q, Queue: %q, SID %s",
|
logTxt = fmt.Sprintf("Subscription Violation - Subject %q, Queue: %q, SID %s", sub.subject, sub.queue, sub.sid)
|
||||||
c.getAuthUser(), sub.subject, sub.queue, sub.sid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.sendErr(errTxt)
|
c.sendErr(errTxt)
|
||||||
@@ -5296,13 +5395,12 @@ func (c *client) replySubjectViolation(reply []byte) {
|
|||||||
mt.setIngressError(errTxt)
|
mt.setIngressError(errTxt)
|
||||||
}
|
}
|
||||||
c.sendErr(errTxt)
|
c.sendErr(errTxt)
|
||||||
c.Errorf("Publish Violation - %s, Reply %q", c.getAuthUser(), reply)
|
c.Errorf("Publish Violation - Reply %q", reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) maxTokensViolation(sub *subscription) {
|
func (c *client) maxTokensViolation(sub *subscription) {
|
||||||
errTxt := fmt.Sprintf("Permissions Violation for Subscription to %q, too many tokens", sub.subject)
|
errTxt := fmt.Sprintf("Permissions Violation for Subscription to %q, too many tokens", sub.subject)
|
||||||
logTxt := fmt.Sprintf("Subscription Violation Too Many Tokens - %s, Subject %q, SID %s",
|
logTxt := fmt.Sprintf("Subscription Violation Too Many Tokens - Subject %q, SID %s", sub.subject, sub.sid)
|
||||||
c.getAuthUser(), sub.subject, sub.sid)
|
|
||||||
c.sendErr(errTxt)
|
c.sendErr(errTxt)
|
||||||
c.Errorf(logTxt)
|
c.Errorf(logTxt)
|
||||||
}
|
}
|
||||||
@@ -5621,10 +5719,8 @@ func (c *client) processSubsOnConfigReload(awcsti map[string]struct{}) {
|
|||||||
// Unsubscribe all that need to be removed and report back to client and logs.
|
// Unsubscribe all that need to be removed and report back to client and logs.
|
||||||
for _, sub := range removed {
|
for _, sub := range removed {
|
||||||
c.unsubscribe(acc, sub, true, true)
|
c.unsubscribe(acc, sub, true, true)
|
||||||
c.sendErr(fmt.Sprintf("Permissions Violation for Subscription to %q (sid %q)",
|
c.sendErr(fmt.Sprintf("Permissions Violation for Subscription to %q (sid %q)", sub.subject, sub.sid))
|
||||||
sub.subject, sub.sid))
|
srv.Noticef("Removed sub %q (sid %q) for %s - not authorized", sub.subject, sub.sid, c.getAuthUser())
|
||||||
srv.Noticef("Removed sub %q (sid %q) for %s - not authorized",
|
|
||||||
sub.subject, sub.sid, c.getAuthUser())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5914,7 +6010,7 @@ func (c *client) getAccAndResultFromCache() (*Account, *SublistResult) {
|
|||||||
|
|
||||||
if genid := atomic.LoadUint64(&sl.genid); genid != pac.genid {
|
if genid := atomic.LoadUint64(&sl.genid); genid != pac.genid {
|
||||||
ok = false
|
ok = false
|
||||||
c.in.pacache = make(map[string]*perAccountCache)
|
clear(c.in.pacache)
|
||||||
} else {
|
} else {
|
||||||
acc = pac.acc
|
acc = pac.acc
|
||||||
r = pac.results
|
r = pac.results
|
||||||
@@ -5937,13 +6033,33 @@ func (c *client) getAccAndResultFromCache() (*Account, *SublistResult) {
|
|||||||
// Match against the account sublist.
|
// Match against the account sublist.
|
||||||
r = sl.MatchBytes(c.pa.subject)
|
r = sl.MatchBytes(c.pa.subject)
|
||||||
|
|
||||||
// Check if we need to prune.
|
// Check if we need to prune. This should give us a perAccountCache struct
|
||||||
|
// to reuse instead of having to allocate a new one.
|
||||||
|
// Previously we would have removed multiple entries but now we will only
|
||||||
|
// prune the minimum number required to maintain the cache size, so that
|
||||||
|
// we reduce the amount of GC pressure and maintain cache stability as best
|
||||||
|
// as possible.
|
||||||
if len(c.in.pacache) >= maxPerAccountCacheSize {
|
if len(c.in.pacache) >= maxPerAccountCacheSize {
|
||||||
c.prunePerAccountCache()
|
for cacheKey, p := range c.in.pacache {
|
||||||
|
delete(c.in.pacache, cacheKey)
|
||||||
|
pac = p
|
||||||
|
if len(c.in.pacache) < maxPerAccountCacheSize {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we can reuse the pac from earlier (i.e. we loaded one but it was an
|
||||||
|
// old generation or we pruned the cache) then do so.
|
||||||
|
if pac == nil {
|
||||||
|
pac = &perAccountCache{}
|
||||||
|
}
|
||||||
|
pac.acc = acc
|
||||||
|
pac.results = r
|
||||||
|
pac.genid = atomic.LoadUint64(&sl.genid)
|
||||||
|
|
||||||
// Store in our cache,make sure to do so after we prune.
|
// Store in our cache,make sure to do so after we prune.
|
||||||
c.in.pacache[string(c.pa.pacache)] = &perAccountCache{acc, r, atomic.LoadUint64(&sl.genid)}
|
c.in.pacache[string(c.pa.pacache)] = pac
|
||||||
}
|
}
|
||||||
return acc, r
|
return acc, r
|
||||||
}
|
}
|
||||||
@@ -5959,17 +6075,6 @@ func (c *client) Account() *Account {
|
|||||||
return acc
|
return acc
|
||||||
}
|
}
|
||||||
|
|
||||||
// prunePerAccountCache will prune off a random number of cache entries.
|
|
||||||
func (c *client) prunePerAccountCache() {
|
|
||||||
n := 0
|
|
||||||
for cacheKey := range c.in.pacache {
|
|
||||||
delete(c.in.pacache, cacheKey)
|
|
||||||
if n++; n > prunePerAccountCacheSize {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pruneClosedSubFromPerAccountCache remove entries that contain subscriptions
|
// pruneClosedSubFromPerAccountCache remove entries that contain subscriptions
|
||||||
// that have been closed.
|
// that have been closed.
|
||||||
func (c *client) pruneClosedSubFromPerAccountCache() {
|
func (c *client) pruneClosedSubFromPerAccountCache() {
|
||||||
@@ -6225,6 +6330,22 @@ func (c *client) getAuthUser() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getAuthUserLabel returns a label for the auth user for the client.
|
||||||
|
func (c *client) getAuthUserLabel() string {
|
||||||
|
switch {
|
||||||
|
case c.opts.Nkey != _EMPTY_:
|
||||||
|
return fmt.Sprintf("nkey:%s", c.opts.Nkey)
|
||||||
|
case c.opts.Username != _EMPTY_:
|
||||||
|
return fmt.Sprintf("user:%s", c.opts.Username)
|
||||||
|
case c.opts.JWT != _EMPTY_:
|
||||||
|
return fmt.Sprintf("jwt:%s", c.pubKey)
|
||||||
|
case c.opts.Token != _EMPTY_:
|
||||||
|
return "token"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Given an array of strings, this function converts it to a map as long
|
// Given an array of strings, this function converts it to a map as long
|
||||||
// as all the content (converted to upper-case) matches some constants.
|
// as all the content (converted to upper-case) matches some constants.
|
||||||
|
|
||||||
@@ -6300,51 +6421,107 @@ func (c *client) isClosed() bool {
|
|||||||
return c.flags.isSet(closeConnection) || c.flags.isSet(connMarkedClosed) || c.nc == nil
|
return c.flags.isSet(closeConnection) || c.flags.isSet(connMarkedClosed) || c.nc == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *client) format(format string) string {
|
||||||
|
if s := c.String(); s != _EMPTY_ {
|
||||||
|
return fmt.Sprintf("%s - %s", s, format)
|
||||||
|
} else {
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) formatNoClientInfo(format string) string {
|
||||||
|
acc := c.ncsAcc.Load()
|
||||||
|
if acc != nil {
|
||||||
|
return fmt.Sprintf("%s - Account:%s", format, acc)
|
||||||
|
} else {
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) formatClientSuffix() string {
|
||||||
|
user := c.ncsUser.Load()
|
||||||
|
if user == nil || user.(string) == _EMPTY_ {
|
||||||
|
return _EMPTY_
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(" - %s", user)
|
||||||
|
}
|
||||||
|
|
||||||
// Logging functionality scoped to a client or route.
|
// Logging functionality scoped to a client or route.
|
||||||
func (c *client) Error(err error) {
|
func (c *client) Error(err error) {
|
||||||
c.srv.Errors(c, err)
|
c.srv.Errorf(c.format(err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) Errorf(format string, v ...any) {
|
func (c *client) Errorf(format string, v ...any) {
|
||||||
format = fmt.Sprintf("%s - %s", c, format)
|
c.srv.Errorf(c.format(format), v...)
|
||||||
c.srv.Errorf(format, v...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) Debugf(format string, v ...any) {
|
func (c *client) Debugf(format string, v ...any) {
|
||||||
format = fmt.Sprintf("%s - %s", c, format)
|
c.srv.Debugf(c.format(format), v...)
|
||||||
c.srv.Debugf(format, v...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) Noticef(format string, v ...any) {
|
func (c *client) Noticef(format string, v ...any) {
|
||||||
format = fmt.Sprintf("%s - %s", c, format)
|
c.srv.Noticef(c.format(format), v...)
|
||||||
c.srv.Noticef(format, v...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) Tracef(format string, v ...any) {
|
func (c *client) Tracef(format string, v ...any) {
|
||||||
format = fmt.Sprintf("%s - %s", c, format)
|
c.srv.Tracef(c.format(format), v...)
|
||||||
c.srv.Tracef(format, v...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) Warnf(format string, v ...any) {
|
func (c *client) Warnf(format string, v ...any) {
|
||||||
format = fmt.Sprintf("%s - %s", c, format)
|
c.srv.Warnf(c.format(format), v...)
|
||||||
c.srv.Warnf(format, v...)
|
}
|
||||||
|
|
||||||
|
func (c *client) RateLimitErrorf(format string, v ...any) {
|
||||||
|
// Do the check before adding the client info to the format...
|
||||||
|
statement := fmt.Sprintf(c.formatNoClientInfo(format), v...)
|
||||||
|
if _, loaded := c.srv.rateLimitLogging.LoadOrStore(statement, time.Now()); loaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s := c.String(); s != _EMPTY_ {
|
||||||
|
c.srv.Errorf("%s - %s%s", c, statement, c.formatClientSuffix())
|
||||||
|
} else {
|
||||||
|
c.srv.Errorf("%s%s", statement, c.formatClientSuffix())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) rateLimitFormatWarnf(format string, v ...any) {
|
func (c *client) rateLimitFormatWarnf(format string, v ...any) {
|
||||||
|
// Do the check before adding the client info to the format...
|
||||||
|
format = c.formatNoClientInfo(format)
|
||||||
if _, loaded := c.srv.rateLimitLogging.LoadOrStore(format, time.Now()); loaded {
|
if _, loaded := c.srv.rateLimitLogging.LoadOrStore(format, time.Now()); loaded {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
statement := fmt.Sprintf(format, v...)
|
statement := fmt.Sprintf(format, v...)
|
||||||
c.Warnf("%s", statement)
|
if s := c.String(); s != _EMPTY_ {
|
||||||
|
c.srv.Warnf("%s - %s%s", c, statement, c.formatClientSuffix())
|
||||||
|
} else {
|
||||||
|
c.srv.Warnf("%s%s", statement, c.formatClientSuffix())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) RateLimitWarnf(format string, v ...any) {
|
func (c *client) RateLimitWarnf(format string, v ...any) {
|
||||||
// Do the check before adding the client info to the format...
|
// Do the check before adding the client info to the format...
|
||||||
statement := fmt.Sprintf(format, v...)
|
statement := fmt.Sprintf(c.formatNoClientInfo(format), v...)
|
||||||
if _, loaded := c.srv.rateLimitLogging.LoadOrStore(statement, time.Now()); loaded {
|
if _, loaded := c.srv.rateLimitLogging.LoadOrStore(statement, time.Now()); loaded {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Warnf("%s", statement)
|
if s := c.String(); s != _EMPTY_ {
|
||||||
|
c.srv.Warnf("%s - %s%s", c, statement, c.formatClientSuffix())
|
||||||
|
} else {
|
||||||
|
c.srv.Warnf("%s%s", statement, c.formatClientSuffix())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) RateLimitDebugf(format string, v ...any) {
|
||||||
|
// Do the check before adding the client info to the format...
|
||||||
|
statement := fmt.Sprintf(c.formatNoClientInfo(format), v...)
|
||||||
|
if _, loaded := c.srv.rateLimitLogging.LoadOrStore(statement, time.Now()); loaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s := c.String(); s != _EMPTY_ {
|
||||||
|
c.srv.Debugf("%s - %s%s", c, statement, c.formatClientSuffix())
|
||||||
|
} else {
|
||||||
|
c.srv.Debugf("%s%s", statement, c.formatClientSuffix())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the very first PING to a lower interval to capture the initial RTT.
|
// Set the very first PING to a lower interval to capture the initial RTT.
|
||||||
@@ -6383,3 +6560,18 @@ func (c *client) setFirstPingTimer() {
|
|||||||
}
|
}
|
||||||
c.ping.tmr = time.AfterFunc(d, c.processPingTimer)
|
c.ping.tmr = time.AfterFunc(d, c.processPingTimer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets this error as the authentication error. To be used in authViolation()
|
||||||
|
// to report an error different of `ErrAuthentication`.
|
||||||
|
func (c *client) setAuthError(err error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
c.authErr = err
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the authentication error set in the connection, possibly nil.
|
||||||
|
func (c *client) getAuthError() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
return c.authErr
|
||||||
|
}
|
||||||
|
|||||||
+5
-2
@@ -66,7 +66,7 @@ func init() {
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// VERSION is the current version for the server.
|
// VERSION is the current version for the server.
|
||||||
VERSION = "2.11.9"
|
VERSION = "2.12.0"
|
||||||
|
|
||||||
// PROTO is the currently supported protocol.
|
// PROTO is the currently supported protocol.
|
||||||
// 0 was the original
|
// 0 was the original
|
||||||
@@ -146,7 +146,10 @@ const (
|
|||||||
// DEFAULT_ROUTE_CONNECT Route solicitation intervals.
|
// DEFAULT_ROUTE_CONNECT Route solicitation intervals.
|
||||||
DEFAULT_ROUTE_CONNECT = 1 * time.Second
|
DEFAULT_ROUTE_CONNECT = 1 * time.Second
|
||||||
|
|
||||||
// DEFAULT_ROUTE_RECONNECT Route reconnect intervals.
|
// DEFAULT_ROUTE_CONNECT_MAX Route solicitation intervals (max).
|
||||||
|
DEFAULT_ROUTE_CONNECT_MAX = 30 * time.Second
|
||||||
|
|
||||||
|
// DEFAULT_ROUTE_RECONNECT Route reconnect delay.
|
||||||
DEFAULT_ROUTE_RECONNECT = 1 * time.Second
|
DEFAULT_ROUTE_RECONNECT = 1 * time.Second
|
||||||
|
|
||||||
// DEFAULT_ROUTE_DIAL Route dial timeout.
|
// DEFAULT_ROUTE_DIAL Route dial timeout.
|
||||||
|
|||||||
+249
-37
@@ -19,6 +19,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -227,18 +228,22 @@ const (
|
|||||||
PriorityOverflow
|
PriorityOverflow
|
||||||
// Single client takes over handling of the messages, while others are on standby.
|
// Single client takes over handling of the messages, while others are on standby.
|
||||||
PriorityPinnedClient
|
PriorityPinnedClient
|
||||||
|
// Clients with lowest priority will be selected first.
|
||||||
|
PriorityPrioritized
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PriorityNoneJSONString = `"none"`
|
PriorityNoneJSONString = `"none"`
|
||||||
PriorityOverflowJSONString = `"overflow"`
|
PriorityOverflowJSONString = `"overflow"`
|
||||||
PriorityPinnedClientJSONString = `"pinned_client"`
|
PriorityPinnedClientJSONString = `"pinned_client"`
|
||||||
|
PriorityPrioritizedJSONString = `"prioritized"`
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
PriorityNoneJSONBytes = []byte(PriorityNoneJSONString)
|
PriorityNoneJSONBytes = []byte(PriorityNoneJSONString)
|
||||||
PriorityOverflowJSONBytes = []byte(PriorityOverflowJSONString)
|
PriorityOverflowJSONBytes = []byte(PriorityOverflowJSONString)
|
||||||
PriorityPinnedClientJSONBytes = []byte(PriorityPinnedClientJSONString)
|
PriorityPinnedClientJSONBytes = []byte(PriorityPinnedClientJSONString)
|
||||||
|
PriorityPrioritizedJSONBytes = []byte(PriorityPrioritizedJSONString)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (pp PriorityPolicy) String() string {
|
func (pp PriorityPolicy) String() string {
|
||||||
@@ -247,6 +252,8 @@ func (pp PriorityPolicy) String() string {
|
|||||||
return PriorityOverflowJSONString
|
return PriorityOverflowJSONString
|
||||||
case PriorityPinnedClient:
|
case PriorityPinnedClient:
|
||||||
return PriorityPinnedClientJSONString
|
return PriorityPinnedClientJSONString
|
||||||
|
case PriorityPrioritized:
|
||||||
|
return PriorityPrioritizedJSONString
|
||||||
default:
|
default:
|
||||||
return PriorityNoneJSONString
|
return PriorityNoneJSONString
|
||||||
}
|
}
|
||||||
@@ -258,6 +265,8 @@ func (pp PriorityPolicy) MarshalJSON() ([]byte, error) {
|
|||||||
return PriorityOverflowJSONBytes, nil
|
return PriorityOverflowJSONBytes, nil
|
||||||
case PriorityPinnedClient:
|
case PriorityPinnedClient:
|
||||||
return PriorityPinnedClientJSONBytes, nil
|
return PriorityPinnedClientJSONBytes, nil
|
||||||
|
case PriorityPrioritized:
|
||||||
|
return PriorityPrioritizedJSONBytes, nil
|
||||||
case PriorityNone:
|
case PriorityNone:
|
||||||
return PriorityNoneJSONBytes, nil
|
return PriorityNoneJSONBytes, nil
|
||||||
default:
|
default:
|
||||||
@@ -271,6 +280,8 @@ func (pp *PriorityPolicy) UnmarshalJSON(data []byte) error {
|
|||||||
*pp = PriorityOverflow
|
*pp = PriorityOverflow
|
||||||
case PriorityPinnedClientJSONString:
|
case PriorityPinnedClientJSONString:
|
||||||
*pp = PriorityPinnedClient
|
*pp = PriorityPinnedClient
|
||||||
|
case PriorityPrioritizedJSONString:
|
||||||
|
*pp = PriorityPrioritized
|
||||||
case PriorityNoneJSONString:
|
case PriorityNoneJSONString:
|
||||||
*pp = PriorityNone
|
*pp = PriorityNone
|
||||||
default:
|
default:
|
||||||
@@ -554,6 +565,63 @@ const (
|
|||||||
|
|
||||||
// Helper function to set consumer config defaults from above.
|
// Helper function to set consumer config defaults from above.
|
||||||
func setConsumerConfigDefaults(config *ConsumerConfig, streamCfg *StreamConfig, lim *JSLimitOpts, accLim *JetStreamAccountLimits, pedantic bool) *ApiError {
|
func setConsumerConfigDefaults(config *ConsumerConfig, streamCfg *StreamConfig, lim *JSLimitOpts, accLim *JetStreamAccountLimits, pedantic bool) *ApiError {
|
||||||
|
// Setup default of -1, meaning no limit for MaxDeliver.
|
||||||
|
if config.MaxDeliver == 0 || config.MaxDeliver < -1 {
|
||||||
|
if pedantic && config.MaxDeliver < -1 {
|
||||||
|
return NewJSPedanticError(errors.New("max_deliver must be set to -1"))
|
||||||
|
}
|
||||||
|
config.MaxDeliver = -1
|
||||||
|
}
|
||||||
|
// Setup zero defaults.
|
||||||
|
if config.MaxWaiting < 0 {
|
||||||
|
if pedantic {
|
||||||
|
return NewJSPedanticError(errors.New("max_waiting must not be negative"))
|
||||||
|
}
|
||||||
|
config.MaxWaiting = 0
|
||||||
|
}
|
||||||
|
if config.MaxAckPending < -1 {
|
||||||
|
if pedantic {
|
||||||
|
return NewJSPedanticError(errors.New("max_ack_pending must be set to -1"))
|
||||||
|
}
|
||||||
|
config.MaxAckPending = -1
|
||||||
|
}
|
||||||
|
if config.MaxRequestBatch < 0 {
|
||||||
|
if pedantic {
|
||||||
|
return NewJSPedanticError(errors.New("max_batch must not be negative"))
|
||||||
|
}
|
||||||
|
config.MaxRequestBatch = 0
|
||||||
|
}
|
||||||
|
if config.MaxRequestExpires < 0 {
|
||||||
|
if pedantic {
|
||||||
|
return NewJSPedanticError(errors.New("max_expires must not be negative"))
|
||||||
|
}
|
||||||
|
config.MaxRequestExpires = 0
|
||||||
|
}
|
||||||
|
if config.MaxRequestMaxBytes < 0 {
|
||||||
|
if pedantic {
|
||||||
|
return NewJSPedanticError(errors.New("max_bytes must not be negative"))
|
||||||
|
}
|
||||||
|
config.MaxRequestMaxBytes = 0
|
||||||
|
}
|
||||||
|
if config.Heartbeat < 0 {
|
||||||
|
if pedantic {
|
||||||
|
return NewJSPedanticError(errors.New("idle_heartbeat must not be negative"))
|
||||||
|
}
|
||||||
|
config.Heartbeat = 0
|
||||||
|
}
|
||||||
|
if config.InactiveThreshold < 0 {
|
||||||
|
if pedantic {
|
||||||
|
return NewJSPedanticError(errors.New("inactive_threshold must not be negative"))
|
||||||
|
}
|
||||||
|
config.InactiveThreshold = 0
|
||||||
|
}
|
||||||
|
if config.PinnedTTL < 0 {
|
||||||
|
if pedantic {
|
||||||
|
return NewJSPedanticError(errors.New("priority_timeout must not be negative"))
|
||||||
|
}
|
||||||
|
config.PinnedTTL = 0
|
||||||
|
}
|
||||||
|
|
||||||
// Set to default if not specified.
|
// Set to default if not specified.
|
||||||
if config.DeliverSubject == _EMPTY_ && config.MaxWaiting == 0 {
|
if config.DeliverSubject == _EMPTY_ && config.MaxWaiting == 0 {
|
||||||
config.MaxWaiting = JSWaitQueueDefaultMax
|
config.MaxWaiting = JSWaitQueueDefaultMax
|
||||||
@@ -562,10 +630,6 @@ func setConsumerConfigDefaults(config *ConsumerConfig, streamCfg *StreamConfig,
|
|||||||
if config.AckWait == 0 && (config.AckPolicy == AckExplicit || config.AckPolicy == AckAll) {
|
if config.AckWait == 0 && (config.AckPolicy == AckExplicit || config.AckPolicy == AckAll) {
|
||||||
config.AckWait = JsAckWaitDefault
|
config.AckWait = JsAckWaitDefault
|
||||||
}
|
}
|
||||||
// Setup default of -1, meaning no limit for MaxDeliver.
|
|
||||||
if config.MaxDeliver == 0 {
|
|
||||||
config.MaxDeliver = -1
|
|
||||||
}
|
|
||||||
// If BackOff was specified that will override the AckWait and the MaxDeliver.
|
// If BackOff was specified that will override the AckWait and the MaxDeliver.
|
||||||
if len(config.BackOff) > 0 {
|
if len(config.BackOff) > 0 {
|
||||||
if pedantic && config.AckWait != config.BackOff[0] {
|
if pedantic && config.AckWait != config.BackOff[0] {
|
||||||
@@ -587,14 +651,14 @@ func setConsumerConfigDefaults(config *ConsumerConfig, streamCfg *StreamConfig,
|
|||||||
}
|
}
|
||||||
// Set proper default for max ack pending if we are ack explicit and none has been set.
|
// Set proper default for max ack pending if we are ack explicit and none has been set.
|
||||||
if (config.AckPolicy == AckExplicit || config.AckPolicy == AckAll) && config.MaxAckPending == 0 {
|
if (config.AckPolicy == AckExplicit || config.AckPolicy == AckAll) && config.MaxAckPending == 0 {
|
||||||
accPending := JsDefaultMaxAckPending
|
ackPending := JsDefaultMaxAckPending
|
||||||
if lim.MaxAckPending > 0 && lim.MaxAckPending < accPending {
|
if lim.MaxAckPending > 0 && lim.MaxAckPending < ackPending {
|
||||||
accPending = lim.MaxAckPending
|
ackPending = lim.MaxAckPending
|
||||||
}
|
}
|
||||||
if accLim.MaxAckPending > 0 && accLim.MaxAckPending < accPending {
|
if accLim.MaxAckPending > 0 && accLim.MaxAckPending < ackPending {
|
||||||
accPending = accLim.MaxAckPending
|
ackPending = accLim.MaxAckPending
|
||||||
}
|
}
|
||||||
config.MaxAckPending = accPending
|
config.MaxAckPending = ackPending
|
||||||
}
|
}
|
||||||
// if applicable set max request batch size
|
// if applicable set max request batch size
|
||||||
if config.DeliverSubject == _EMPTY_ && config.MaxRequestBatch == 0 && lim.MaxRequestBatch > 0 {
|
if config.DeliverSubject == _EMPTY_ && config.MaxRequestBatch == 0 && lim.MaxRequestBatch > 0 {
|
||||||
@@ -640,6 +704,23 @@ func checkConsumerCfg(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := config.AckPolicy.MarshalJSON(); err != nil {
|
||||||
|
return NewJSConsumerAckPolicyInvalidError()
|
||||||
|
}
|
||||||
|
if _, err := config.ReplayPolicy.MarshalJSON(); err != nil {
|
||||||
|
return NewJSConsumerReplayPolicyInvalidError()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check not negative AckWait/BackOff
|
||||||
|
for _, backoff := range config.BackOff {
|
||||||
|
if backoff < 0 {
|
||||||
|
return NewJSConsumerBackOffNegativeError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if config.AckWait < 0 {
|
||||||
|
return NewJSConsumerAckWaitNegativeError()
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we have a BackOff defined that MaxDeliver is within range etc.
|
// Check if we have a BackOff defined that MaxDeliver is within range etc.
|
||||||
if lbo := len(config.BackOff); lbo > 0 && config.MaxDeliver != -1 && lbo > config.MaxDeliver {
|
if lbo := len(config.BackOff); lbo > 0 && config.MaxDeliver != -1 && lbo > config.MaxDeliver {
|
||||||
return NewJSConsumerMaxDeliverBackoffError()
|
return NewJSConsumerMaxDeliverBackoffError()
|
||||||
@@ -690,7 +771,7 @@ func checkConsumerCfg(
|
|||||||
return NewJSConsumerMaxRequestBatchNegativeError()
|
return NewJSConsumerMaxRequestBatchNegativeError()
|
||||||
}
|
}
|
||||||
if config.MaxRequestExpires != 0 && config.MaxRequestExpires < time.Millisecond {
|
if config.MaxRequestExpires != 0 && config.MaxRequestExpires < time.Millisecond {
|
||||||
return NewJSConsumerMaxRequestExpiresToSmallError()
|
return NewJSConsumerMaxRequestExpiresTooSmallError()
|
||||||
}
|
}
|
||||||
if srvLim.MaxRequestBatch > 0 && config.MaxRequestBatch > srvLim.MaxRequestBatch {
|
if srvLim.MaxRequestBatch > 0 && config.MaxRequestBatch > srvLim.MaxRequestBatch {
|
||||||
return NewJSConsumerMaxRequestBatchExceededError(srvLim.MaxRequestBatch)
|
return NewJSConsumerMaxRequestBatchExceededError(srvLim.MaxRequestBatch)
|
||||||
@@ -849,6 +930,14 @@ func checkConsumerCfg(
|
|||||||
return NewJSConsumerInvalidGroupNameError()
|
return NewJSConsumerInvalidGroupNameError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// If PriorityPolicy is None or not set, reject if PriorityGroups or PinnedTTL are set
|
||||||
|
if len(config.PriorityGroups) > 0 {
|
||||||
|
return NewJSConsumerPriorityGroupWithPolicyNoneError()
|
||||||
|
}
|
||||||
|
if config.PinnedTTL > 0 {
|
||||||
|
return NewJSConsumerPinnedTTLWithoutPriorityPolicyNoneError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For now don't allow preferred server in placement.
|
// For now don't allow preferred server in placement.
|
||||||
@@ -3445,6 +3534,7 @@ type PriorityGroup struct {
|
|||||||
MinPending int64 `json:"min_pending,omitempty"`
|
MinPending int64 `json:"min_pending,omitempty"`
|
||||||
MinAckPending int64 `json:"min_ack_pending,omitempty"`
|
MinAckPending int64 `json:"min_ack_pending,omitempty"`
|
||||||
Id string `json:"id,omitempty"`
|
Id string `json:"id,omitempty"`
|
||||||
|
Priority int `json:"priority,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used in nextReqFromMsg, since the json.Unmarshal causes the request
|
// Used in nextReqFromMsg, since the json.Unmarshal causes the request
|
||||||
@@ -3574,6 +3664,33 @@ var (
|
|||||||
errWaitQueueNil = errors.New("wait queue is nil")
|
errWaitQueueNil = errors.New("wait queue is nil")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// insertSorted inserts wr at the correct position based on priority
|
||||||
|
func (wq *waitQueue) insertSorted(wr *waitingRequest) {
|
||||||
|
|
||||||
|
// Handle empty queue
|
||||||
|
if wq.head == nil {
|
||||||
|
wq.head = wr
|
||||||
|
wq.tail = wr
|
||||||
|
wr.next = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
insertAtPosition(wr, wq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wq *waitQueue) addPrioritized(wr *waitingRequest) error {
|
||||||
|
if wq == nil {
|
||||||
|
return errWaitQueueNil
|
||||||
|
}
|
||||||
|
if wq.isFull() {
|
||||||
|
return errWaitQueueFull
|
||||||
|
}
|
||||||
|
|
||||||
|
wq.insertSorted(wr)
|
||||||
|
wq.n++
|
||||||
|
wq.last = wr.received
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Adds in a new request.
|
// Adds in a new request.
|
||||||
func (wq *waitQueue) add(wr *waitingRequest) error {
|
func (wq *waitQueue) add(wr *waitingRequest) error {
|
||||||
if wq == nil {
|
if wq == nil {
|
||||||
@@ -3637,6 +3754,17 @@ func (wq *waitQueue) cycle() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wq *waitQueue) popOrPopAndRequeue(priority PriorityPolicy) *waitingRequest {
|
||||||
|
if wq == nil || wq.head == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if priority == PriorityPrioritized {
|
||||||
|
return wq.popAndRequeue()
|
||||||
|
}
|
||||||
|
return wq.pop()
|
||||||
|
}
|
||||||
|
|
||||||
// pop will return the next request and move the read cursor.
|
// pop will return the next request and move the read cursor.
|
||||||
// This will now place a request that still has pending items at the ends of the list.
|
// This will now place a request that still has pending items at the ends of the list.
|
||||||
func (wq *waitQueue) pop() *waitingRequest {
|
func (wq *waitQueue) pop() *waitingRequest {
|
||||||
@@ -3656,6 +3784,74 @@ func (wq *waitQueue) pop() *waitingRequest {
|
|||||||
return wr
|
return wr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// popAndRequeue pops the head element and requeues it at the end of its priority group.
|
||||||
|
// This maintains FIFO order within the same priority level.
|
||||||
|
func (wq *waitQueue) popAndRequeue() *waitingRequest {
|
||||||
|
if wq == nil || wq.head == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the head
|
||||||
|
wr := wq.head
|
||||||
|
|
||||||
|
if wr == nil {
|
||||||
|
return wr
|
||||||
|
}
|
||||||
|
|
||||||
|
wr.d++
|
||||||
|
wr.n--
|
||||||
|
|
||||||
|
if wr.n > 0 && wq.n > 1 {
|
||||||
|
if wr.next == nil {
|
||||||
|
return wr
|
||||||
|
}
|
||||||
|
wq.head = wq.head.next
|
||||||
|
wr.next = nil
|
||||||
|
|
||||||
|
insertAtPosition(wr, wq)
|
||||||
|
|
||||||
|
} else if wr.n <= 0 {
|
||||||
|
wq.removeCurrent()
|
||||||
|
return wr
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return wr
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertAtPosition(wr *waitingRequest, wq *waitQueue) {
|
||||||
|
priority := math.MaxInt32
|
||||||
|
if wr.priorityGroup != nil {
|
||||||
|
priority = wr.priorityGroup.Priority
|
||||||
|
}
|
||||||
|
|
||||||
|
var prev *waitingRequest
|
||||||
|
current := wq.head
|
||||||
|
for current != nil {
|
||||||
|
currentPriority := math.MaxInt32
|
||||||
|
if current.priorityGroup != nil {
|
||||||
|
currentPriority = current.priorityGroup.Priority
|
||||||
|
}
|
||||||
|
if currentPriority > priority {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prev = current
|
||||||
|
current = current.next
|
||||||
|
}
|
||||||
|
|
||||||
|
if prev == nil {
|
||||||
|
// All remaining elements have higher priority
|
||||||
|
wr.next = wq.head
|
||||||
|
wq.head = wr
|
||||||
|
} else {
|
||||||
|
wr.next = prev.next
|
||||||
|
prev.next = wr
|
||||||
|
if wr.next == nil {
|
||||||
|
wq.tail = wr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Removes the current read pointer (head FIFO) entry.
|
// Removes the current read pointer (head FIFO) entry.
|
||||||
func (wq *waitQueue) removeCurrent() {
|
func (wq *waitQueue) removeCurrent() {
|
||||||
wq.remove(nil, wq.head)
|
wq.remove(nil, wq.head)
|
||||||
@@ -3817,17 +4013,17 @@ func (o *consumer) nextWaiting(sz int) *waitingRequest {
|
|||||||
if needNewPin {
|
if needNewPin {
|
||||||
o.sendPinnedAdvisoryLocked(priorityGroup)
|
o.sendPinnedAdvisoryLocked(priorityGroup)
|
||||||
}
|
}
|
||||||
return o.waiting.pop()
|
return o.waiting.popOrPopAndRequeue(o.cfg.PriorityPolicy)
|
||||||
} else if time.Since(wr.received) < defaultGatewayRecentSubExpiration && (o.srv.leafNodeEnabled || o.srv.gateway.enabled) {
|
} else if time.Since(wr.received) < defaultGatewayRecentSubExpiration && (o.srv.leafNodeEnabled || o.srv.gateway.enabled) {
|
||||||
if needNewPin {
|
if needNewPin {
|
||||||
o.sendPinnedAdvisoryLocked(priorityGroup)
|
o.sendPinnedAdvisoryLocked(priorityGroup)
|
||||||
}
|
}
|
||||||
return o.waiting.pop()
|
return o.waiting.popOrPopAndRequeue(o.cfg.PriorityPolicy)
|
||||||
} else if o.srv.gateway.enabled && o.srv.hasGatewayInterest(wr.acc.Name, wr.interest) {
|
} else if o.srv.gateway.enabled && o.srv.hasGatewayInterest(wr.acc.Name, wr.interest) {
|
||||||
if needNewPin {
|
if needNewPin {
|
||||||
o.sendPinnedAdvisoryLocked(priorityGroup)
|
o.sendPinnedAdvisoryLocked(priorityGroup)
|
||||||
}
|
}
|
||||||
return o.waiting.pop()
|
return o.waiting.popOrPopAndRequeue(o.cfg.PriorityPolicy)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We do check for expiration in `processWaiting`, but it is possible to hit the expiry here, and not there.
|
// We do check for expiration in `processWaiting`, but it is possible to hit the expiry here, and not there.
|
||||||
@@ -3902,7 +4098,7 @@ func (nmr *nextMsgReq) returnToPool() {
|
|||||||
// processNextMsgReq will process a request for the next message available. A nil message payload means deliver
|
// processNextMsgReq will process a request for the next message available. A nil message payload means deliver
|
||||||
// a single message. If the payload is a formal request or a number parseable with Atoi(), then we will send a
|
// a single message. If the payload is a formal request or a number parseable with Atoi(), then we will send a
|
||||||
// batch of messages without requiring another request to this endpoint, or an ACK.
|
// batch of messages without requiring another request to this endpoint, or an ACK.
|
||||||
func (o *consumer) processNextMsgReq(_ *subscription, c *client, _ *Account, _, reply string, msg []byte) {
|
func (o *consumer) processNextMsgReq(_ *subscription, c *client, _ *Account, _, reply string, rmsg []byte) {
|
||||||
if reply == _EMPTY_ {
|
if reply == _EMPTY_ {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -3914,7 +4110,12 @@ func (o *consumer) processNextMsgReq(_ *subscription, c *client, _ *Account, _,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, msg = c.msgParts(msg)
|
hdr, msg := c.msgParts(rmsg)
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
hdr = []byte("NATS/1.0 412 Required Api Level\r\n\r\n")
|
||||||
|
o.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0))
|
||||||
|
return
|
||||||
|
}
|
||||||
o.nextMsgReqs.push(newNextMsgReq(reply, copyBytes(msg)))
|
o.nextMsgReqs.push(newNextMsgReq(reply, copyBytes(msg)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3968,6 +4169,10 @@ func (o *consumer) processNextMsgRequest(reply string, msg []byte) {
|
|||||||
if priorityGroup.Id != _EMPTY_ && o.cfg.PriorityPolicy != PriorityPinnedClient {
|
if priorityGroup.Id != _EMPTY_ && o.cfg.PriorityPolicy != PriorityPinnedClient {
|
||||||
sendErr(400, "Bad Request - Not a Pinned Client Priority consumer")
|
sendErr(400, "Bad Request - Not a Pinned Client Priority consumer")
|
||||||
}
|
}
|
||||||
|
if priorityGroup.Priority < 0 || priorityGroup.Priority > 9 {
|
||||||
|
sendErr(400, "Bad Request - Priority must be between 0 and 9")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if priorityGroup != nil && o.cfg.PriorityPolicy != PriorityNone {
|
if priorityGroup != nil && o.cfg.PriorityPolicy != PriorityNone {
|
||||||
@@ -4038,15 +4243,25 @@ func (o *consumer) processNextMsgRequest(reply string, msg []byte) {
|
|||||||
wr.b = maxBytes
|
wr.b = maxBytes
|
||||||
wr.received = time.Now()
|
wr.received = time.Now()
|
||||||
|
|
||||||
if err := o.waiting.add(wr); err != nil {
|
if o.cfg.PriorityPolicy == PriorityPrioritized {
|
||||||
// If the client has a heartbeat interval set, don't bother responding with a 409,
|
if err := o.waiting.addPrioritized(wr); err != nil {
|
||||||
// otherwise we can end up in a hot loop with the client re-requesting instead of
|
if hb == 0 {
|
||||||
// waiting for the missing heartbeats instead and retrying.
|
sendErr(409, "Exceeded MaxWaiting")
|
||||||
if hb == 0 {
|
}
|
||||||
sendErr(409, "Exceeded MaxWaiting")
|
wr.recycle()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := o.waiting.add(wr); err != nil {
|
||||||
|
// If the client has a heartbeat interval set, don't bother responding with a 409,
|
||||||
|
// otherwise we can end up in a hot loop with the client re-requesting instead of
|
||||||
|
// waiting for the missing heartbeats instead and retrying.
|
||||||
|
if hb == 0 {
|
||||||
|
sendErr(409, "Exceeded MaxWaiting")
|
||||||
|
}
|
||||||
|
wr.recycle()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
wr.recycle()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
o.signalNewMessages()
|
o.signalNewMessages()
|
||||||
// If we are clustered update our followers about this request.
|
// If we are clustered update our followers about this request.
|
||||||
@@ -4243,9 +4458,6 @@ func (o *consumer) getNextMsg() (*jsPubMsg, uint64, error) {
|
|||||||
return pmsg, 1, err
|
return pmsg, 1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hold onto this since we release the lock.
|
|
||||||
store := o.mset.store
|
|
||||||
|
|
||||||
var sseq uint64
|
var sseq uint64
|
||||||
var err error
|
var err error
|
||||||
var sm *StoreMsg
|
var sm *StoreMsg
|
||||||
@@ -4255,13 +4467,13 @@ func (o *consumer) getNextMsg() (*jsPubMsg, uint64, error) {
|
|||||||
filters, subjf, fseq := o.filters, o.subjf, o.sseq
|
filters, subjf, fseq := o.filters, o.subjf, o.sseq
|
||||||
// Check if we are multi-filtered or not.
|
// Check if we are multi-filtered or not.
|
||||||
if filters != nil {
|
if filters != nil {
|
||||||
sm, sseq, err = store.LoadNextMsgMulti(filters, fseq, &pmsg.StoreMsg)
|
sm, sseq, err = o.mset.store.LoadNextMsgMulti(filters, fseq, &pmsg.StoreMsg)
|
||||||
} else if len(subjf) > 0 { // Means single filtered subject since o.filters means > 1.
|
} else if len(subjf) > 0 { // Means single filtered subject since o.filters means > 1.
|
||||||
filter, wc := subjf[0].subject, subjf[0].hasWildcard
|
filter, wc := subjf[0].subject, subjf[0].hasWildcard
|
||||||
sm, sseq, err = store.LoadNextMsg(filter, wc, fseq, &pmsg.StoreMsg)
|
sm, sseq, err = o.mset.store.LoadNextMsg(filter, wc, fseq, &pmsg.StoreMsg)
|
||||||
} else {
|
} else {
|
||||||
// No filter here.
|
// No filter here.
|
||||||
sm, sseq, err = store.LoadNextMsg(_EMPTY_, false, fseq, &pmsg.StoreMsg)
|
sm, sseq, err = o.mset.store.LoadNextMsg(_EMPTY_, false, fseq, &pmsg.StoreMsg)
|
||||||
}
|
}
|
||||||
if sm == nil {
|
if sm == nil {
|
||||||
pmsg.returnToPool()
|
pmsg.returnToPool()
|
||||||
@@ -4670,14 +4882,14 @@ func (o *consumer) loopAndGatherMsgs(qch chan struct{}) {
|
|||||||
}
|
}
|
||||||
if err == ErrStoreMsgNotFound || err == errDeletedMsg || err == ErrStoreEOF || err == errMaxAckPending {
|
if err == ErrStoreMsgNotFound || err == errDeletedMsg || err == ErrStoreEOF || err == errMaxAckPending {
|
||||||
goto waitForMsgs
|
goto waitForMsgs
|
||||||
} else if err == errPartialCache {
|
|
||||||
s.Warnf("Unexpected partial cache error looking up message for consumer '%s > %s > %s'",
|
|
||||||
o.mset.acc, stream, o.cfg.Name)
|
|
||||||
goto waitForMsgs
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
s.Errorf("Received an error looking up message for consumer '%s > %s > %s': %v",
|
if pmsg != nil {
|
||||||
o.mset.acc, stream, o.cfg.Name, err)
|
s.Errorf("Received an error looking up message with sequence %d for consumer '%s > %s > %s': %v",
|
||||||
|
pmsg.seq, o.mset.acc, stream, o.cfg.Name, err)
|
||||||
|
} else {
|
||||||
|
s.Errorf("Received an error looking up message for consumer '%s > %s > %s': %v",
|
||||||
|
o.mset.acc, stream, o.cfg.Name, err)
|
||||||
|
}
|
||||||
goto waitForMsgs
|
goto waitForMsgs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2025 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package elastic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"weak"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Make[T any](ptr *T) *Pointer[T] {
|
||||||
|
return &Pointer[T]{
|
||||||
|
weak: weak.Make(ptr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pointer[T any] struct {
|
||||||
|
weak weak.Pointer[T]
|
||||||
|
strong *T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Pointer[T]) Set(ptr *T) {
|
||||||
|
e.weak = weak.Make(ptr)
|
||||||
|
if e.strong != nil {
|
||||||
|
e.strong = ptr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Pointer[T]) Strengthen() {
|
||||||
|
if e == nil || e.strong != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.strong = e.weak.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Pointer[T]) Weaken() {
|
||||||
|
if e == nil || e.strong == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.strong = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Pointer[T]) Value() *T {
|
||||||
|
if e == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if e.strong != nil {
|
||||||
|
return e.strong
|
||||||
|
}
|
||||||
|
return e.weak.Value()
|
||||||
|
}
|
||||||
+8
@@ -31,6 +31,14 @@ var (
|
|||||||
// ErrAuthExpired represents an expired authorization due to timeout.
|
// ErrAuthExpired represents an expired authorization due to timeout.
|
||||||
ErrAuthExpired = errors.New("authentication expired")
|
ErrAuthExpired = errors.New("authentication expired")
|
||||||
|
|
||||||
|
// ErrAuthProxyNotTrusted represents an error condition on failed authentication
|
||||||
|
// due to a connection from a proxy not in the list of trusted proxies.
|
||||||
|
ErrAuthProxyNotTrusted = errors.New("proxy is not trusted")
|
||||||
|
|
||||||
|
// ErrAuthProxyRequired represents an error condition on failed authentication
|
||||||
|
// due to a connection not coming from a proxy.
|
||||||
|
ErrAuthProxyRequired = errors.New("proxy connection required")
|
||||||
|
|
||||||
// ErrMaxPayload represents an error condition when the payload is too big.
|
// ErrMaxPayload represents an error condition when the payload is too big.
|
||||||
ErrMaxPayload = errors.New("maximum payload exceeded")
|
ErrMaxPayload = errors.New("maximum payload exceeded")
|
||||||
|
|
||||||
|
|||||||
+291
-1
@@ -1140,7 +1140,7 @@
|
|||||||
"deprecates": ""
|
"deprecates": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"constant": "JSConsumerMaxRequestExpiresToSmall",
|
"constant": "JSConsumerMaxRequestExpiresTooSmall",
|
||||||
"code": 400,
|
"code": 400,
|
||||||
"error_code": 10115,
|
"error_code": 10115,
|
||||||
"description": "consumer max request expires needs to be \u003e= 1ms",
|
"description": "consumer max request expires needs to be \u003e= 1ms",
|
||||||
@@ -1659,6 +1659,106 @@
|
|||||||
"url": "",
|
"url": "",
|
||||||
"deprecates": ""
|
"deprecates": ""
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSMessageIncrDisabledErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10168,
|
||||||
|
"description": "message counters is disabled",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSMessageIncrMissingErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10169,
|
||||||
|
"description": "message counter increment is missing",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSMessageIncrPayloadErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10170,
|
||||||
|
"description": "message counter has payload",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSMessageIncrInvalidErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10171,
|
||||||
|
"description": "message counter increment is invalid",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSMessageCounterBrokenErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10172,
|
||||||
|
"description": "message counter is broken",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSMirrorWithCountersErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10173,
|
||||||
|
"description": "stream mirrors can not also calculate counters",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSAtomicPublishDisabledErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10174,
|
||||||
|
"description": "atomic publish is disabled",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSAtomicPublishMissingSeqErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10175,
|
||||||
|
"description": "atomic publish sequence is missing",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSAtomicPublishIncompleteBatchErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10176,
|
||||||
|
"description": "atomic publish batch is incomplete",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSAtomicPublishUnsupportedHeaderBatchErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10177,
|
||||||
|
"description": "atomic publish unsupported header used: {header}",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"constant": "JSConsumerPushWithPriorityGroupErr",
|
"constant": "JSConsumerPushWithPriorityGroupErr",
|
||||||
"code": 400,
|
"code": 400,
|
||||||
@@ -1669,6 +1769,156 @@
|
|||||||
"url": "",
|
"url": "",
|
||||||
"deprecates": ""
|
"deprecates": ""
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSAtomicPublishInvalidBatchIDErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10179,
|
||||||
|
"description": "atomic publish batch ID is invalid",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSStreamMinLastSeqErr",
|
||||||
|
"code": 412,
|
||||||
|
"error_code": 10180,
|
||||||
|
"description": "min last sequence",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSConsumerAckPolicyInvalidErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10181,
|
||||||
|
"description": "consumer ack policy invalid",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSConsumerReplayPolicyInvalidErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10182,
|
||||||
|
"description": "consumer replay policy invalid",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSConsumerAckWaitNegativeErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10183,
|
||||||
|
"description": "consumer ack wait needs to be positive",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSConsumerBackOffNegativeErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10184,
|
||||||
|
"description": "consumer backoff needs to be positive",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSRequiredApiLevelErr",
|
||||||
|
"code": 412,
|
||||||
|
"error_code": 10185,
|
||||||
|
"description": "JetStream minimum api level required",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSMirrorWithMsgSchedulesErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10186,
|
||||||
|
"description": "stream mirrors can not also schedule messages",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSSourceWithMsgSchedulesErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10187,
|
||||||
|
"description": "stream source can not also schedule messages",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSMessageSchedulesDisabledErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10188,
|
||||||
|
"description": "message schedules is disabled",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSMessageSchedulesPatternInvalidErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10189,
|
||||||
|
"description": "message schedules pattern is invalid",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSMessageSchedulesTargetInvalidErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10190,
|
||||||
|
"description": "message schedules target is invalid",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSMessageSchedulesTTLInvalidErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10191,
|
||||||
|
"description": "message schedules invalid per-message TTL",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSMessageSchedulesRollupInvalidErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10192,
|
||||||
|
"description": "message schedules invalid rollup",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSStreamExpectedLastSeqPerSubjectInvalid",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10193,
|
||||||
|
"description": "missing sequence for expected last sequence per subject",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"constant": "JSStreamOfflineReasonErrF",
|
"constant": "JSStreamOfflineReasonErrF",
|
||||||
"code": 500,
|
"code": 500,
|
||||||
@@ -1688,5 +1938,45 @@
|
|||||||
"help": "",
|
"help": "",
|
||||||
"url": "",
|
"url": "",
|
||||||
"deprecates": ""
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSConsumerPriorityGroupWithPolicyNone",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10196,
|
||||||
|
"description": "consumer can not have priority groups when policy is none",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSConsumerPinnedTTLWithoutPriorityPolicyNone",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10197,
|
||||||
|
"description": "PinnedTTL cannot be set when PriorityPolicy is none",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSMirrorWithAtomicPublishErr",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10198,
|
||||||
|
"description": "stream mirrors can not also use atomic publishing",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"constant": "JSAtomicPublishTooLargeBatchErrF",
|
||||||
|
"code": 400,
|
||||||
|
"error_code": 10199,
|
||||||
|
"description": "atomic publish batch is too large: {size}",
|
||||||
|
"comment": "",
|
||||||
|
"help": "",
|
||||||
|
"url": "",
|
||||||
|
"deprecates": ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+87
-71
@@ -247,13 +247,14 @@ type ServerCapability uint64
|
|||||||
|
|
||||||
// ServerInfo identifies remote servers.
|
// ServerInfo identifies remote servers.
|
||||||
type ServerInfo struct {
|
type ServerInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Cluster string `json:"cluster,omitempty"`
|
Cluster string `json:"cluster,omitempty"`
|
||||||
Domain string `json:"domain,omitempty"`
|
Domain string `json:"domain,omitempty"`
|
||||||
Version string `json:"ver"`
|
Version string `json:"ver"`
|
||||||
Tags []string `json:"tags,omitempty"`
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
Metadata map[string]string `json:"metadata,omitempty"`
|
||||||
// Whether JetStream is enabled (deprecated in favor of the `ServerCapability`).
|
// Whether JetStream is enabled (deprecated in favor of the `ServerCapability`).
|
||||||
JetStream bool `json:"jetstream"`
|
JetStream bool `json:"jetstream"`
|
||||||
// Generic capability flags
|
// Generic capability flags
|
||||||
@@ -362,24 +363,27 @@ func (ci *ClientInfo) forAdvisory() *ClientInfo {
|
|||||||
|
|
||||||
// ServerStats hold various statistics that we will periodically send out.
|
// ServerStats hold various statistics that we will periodically send out.
|
||||||
type ServerStats struct {
|
type ServerStats struct {
|
||||||
Start time.Time `json:"start"`
|
Start time.Time `json:"start"`
|
||||||
Mem int64 `json:"mem"`
|
Mem int64 `json:"mem"`
|
||||||
Cores int `json:"cores"`
|
Cores int `json:"cores"`
|
||||||
CPU float64 `json:"cpu"`
|
CPU float64 `json:"cpu"`
|
||||||
Connections int `json:"connections"`
|
Connections int `json:"connections"`
|
||||||
TotalConnections uint64 `json:"total_connections"`
|
TotalConnections uint64 `json:"total_connections"`
|
||||||
ActiveAccounts int `json:"active_accounts"`
|
ActiveAccounts int `json:"active_accounts"`
|
||||||
NumSubs uint32 `json:"subscriptions"`
|
NumSubs uint32 `json:"subscriptions"`
|
||||||
Sent DataStats `json:"sent"`
|
Sent DataStats `json:"sent"`
|
||||||
Received DataStats `json:"received"`
|
Received DataStats `json:"received"`
|
||||||
SlowConsumers int64 `json:"slow_consumers"`
|
SlowConsumers int64 `json:"slow_consumers"`
|
||||||
SlowConsumersStats *SlowConsumersStats `json:"slow_consumer_stats,omitempty"`
|
SlowConsumersStats *SlowConsumersStats `json:"slow_consumer_stats,omitempty"`
|
||||||
Routes []*RouteStat `json:"routes,omitempty"`
|
StaleConnections int64 `json:"stale_connections,omitempty"`
|
||||||
Gateways []*GatewayStat `json:"gateways,omitempty"`
|
StaleConnectionStats *StaleConnectionStats `json:"stale_connection_stats,omitempty"`
|
||||||
ActiveServers int `json:"active_servers,omitempty"`
|
StalledClients int64 `json:"stalled_clients,omitempty"`
|
||||||
JetStream *JetStreamVarz `json:"jetstream,omitempty"`
|
Routes []*RouteStat `json:"routes,omitempty"`
|
||||||
MemLimit int64 `json:"gomemlimit,omitempty"`
|
Gateways []*GatewayStat `json:"gateways,omitempty"`
|
||||||
MaxProcs int `json:"gomaxprocs,omitempty"`
|
ActiveServers int `json:"active_servers,omitempty"`
|
||||||
|
JetStream *JetStreamVarz `json:"jetstream,omitempty"`
|
||||||
|
MemLimit int64 `json:"gomemlimit,omitempty"`
|
||||||
|
MaxProcs int `json:"gomaxprocs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RouteStat holds route statistics.
|
// RouteStat holds route statistics.
|
||||||
@@ -400,17 +404,17 @@ type GatewayStat struct {
|
|||||||
NumInbound int `json:"inbound_connections"`
|
NumInbound int `json:"inbound_connections"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type dataStats struct {
|
type MsgBytes struct {
|
||||||
Msgs int64 `json:"msgs"`
|
Msgs int64 `json:"msgs"`
|
||||||
Bytes int64 `json:"bytes"`
|
Bytes int64 `json:"bytes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DataStats reports how may msg and bytes. Applicable for both sent and received.
|
// DataStats reports how may msg and bytes. Applicable for both sent and received.
|
||||||
type DataStats struct {
|
type DataStats struct {
|
||||||
dataStats
|
MsgBytes
|
||||||
Gateways dataStats `json:"gateways,omitempty"`
|
Gateways *MsgBytes `json:"gateways,omitempty"`
|
||||||
Routes dataStats `json:"routes,omitempty"`
|
Routes *MsgBytes `json:"routes,omitempty"`
|
||||||
Leafs dataStats `json:"leafs,omitempty"`
|
Leafs *MsgBytes `json:"leafs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used for internally queueing up messages that the server wants to send.
|
// Used for internally queueing up messages that the server wants to send.
|
||||||
@@ -512,8 +516,9 @@ RESET:
|
|||||||
}
|
}
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
|
|
||||||
// Grab tags.
|
// Grab tags and metadata.
|
||||||
tags := s.getOpts().Tags
|
opts := s.getOpts()
|
||||||
|
tags, metadata := opts.Tags, opts.Metadata
|
||||||
|
|
||||||
for s.eventsRunning() {
|
for s.eventsRunning() {
|
||||||
select {
|
select {
|
||||||
@@ -530,6 +535,7 @@ RESET:
|
|||||||
si.Version = VERSION
|
si.Version = VERSION
|
||||||
si.Time = time.Now().UTC()
|
si.Time = time.Now().UTC()
|
||||||
si.Tags = tags
|
si.Tags = tags
|
||||||
|
si.Metadata = metadata
|
||||||
si.Flags = 0
|
si.Flags = 0
|
||||||
if js {
|
if js {
|
||||||
// New capability based flags.
|
// New capability based flags.
|
||||||
@@ -860,13 +866,13 @@ func routeStat(r *client) *RouteStat {
|
|||||||
rs := &RouteStat{
|
rs := &RouteStat{
|
||||||
ID: r.cid,
|
ID: r.cid,
|
||||||
Sent: DataStats{
|
Sent: DataStats{
|
||||||
dataStats: dataStats{
|
MsgBytes: MsgBytes{
|
||||||
Msgs: r.outMsgs,
|
Msgs: r.outMsgs,
|
||||||
Bytes: r.outBytes,
|
Bytes: r.outBytes,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Received: DataStats{
|
Received: DataStats{
|
||||||
dataStats: dataStats{
|
MsgBytes: MsgBytes{
|
||||||
Msgs: atomic.LoadInt64(&r.inMsgs),
|
Msgs: atomic.LoadInt64(&r.inMsgs),
|
||||||
Bytes: atomic.LoadInt64(&r.inBytes),
|
Bytes: atomic.LoadInt64(&r.inBytes),
|
||||||
},
|
},
|
||||||
@@ -952,6 +958,17 @@ func (s *Server) sendStatsz(subj string) {
|
|||||||
if scs.Clients != 0 || scs.Routes != 0 || scs.Gateways != 0 || scs.Leafs != 0 {
|
if scs.Clients != 0 || scs.Routes != 0 || scs.Gateways != 0 || scs.Leafs != 0 {
|
||||||
m.Stats.SlowConsumersStats = scs
|
m.Stats.SlowConsumersStats = scs
|
||||||
}
|
}
|
||||||
|
m.Stats.StaleConnections = atomic.LoadInt64(&s.staleConnections)
|
||||||
|
m.Stats.StalledClients = atomic.LoadInt64(&s.stalls)
|
||||||
|
stcs := &StaleConnectionStats{
|
||||||
|
Clients: s.NumStaleConnectionsClients(),
|
||||||
|
Routes: s.NumStaleConnectionsRoutes(),
|
||||||
|
Gateways: s.NumStaleConnectionsGateways(),
|
||||||
|
Leafs: s.NumStaleConnectionsLeafs(),
|
||||||
|
}
|
||||||
|
if stcs.Clients != 0 || stcs.Routes != 0 || stcs.Gateways != 0 || stcs.Leafs != 0 {
|
||||||
|
m.Stats.StaleConnectionStats = stcs
|
||||||
|
}
|
||||||
m.Stats.NumSubs = s.numSubscriptions()
|
m.Stats.NumSubs = s.numSubscriptions()
|
||||||
// Routes
|
// Routes
|
||||||
s.forEachRoute(func(r *client) {
|
s.forEachRoute(func(r *client) {
|
||||||
@@ -968,7 +985,7 @@ func (s *Server) sendStatsz(subj string) {
|
|||||||
// Note that *client.out[Msgs|Bytes] are not set using atomic,
|
// Note that *client.out[Msgs|Bytes] are not set using atomic,
|
||||||
// unlike the in[Msgs|bytes].
|
// unlike the in[Msgs|bytes].
|
||||||
gs.Sent = DataStats{
|
gs.Sent = DataStats{
|
||||||
dataStats: dataStats{
|
MsgBytes: MsgBytes{
|
||||||
Msgs: c.outMsgs,
|
Msgs: c.outMsgs,
|
||||||
Bytes: c.outBytes,
|
Bytes: c.outBytes,
|
||||||
},
|
},
|
||||||
@@ -1927,11 +1944,12 @@ func (s *Server) leafNodeConnected(sub *subscription, _ *client, _ *Account, sub
|
|||||||
|
|
||||||
// Common filter options for system requests STATSZ VARZ SUBSZ CONNZ ROUTEZ GATEWAYZ LEAFZ
|
// Common filter options for system requests STATSZ VARZ SUBSZ CONNZ ROUTEZ GATEWAYZ LEAFZ
|
||||||
type EventFilterOptions struct {
|
type EventFilterOptions struct {
|
||||||
Name string `json:"server_name,omitempty"` // filter by server name
|
Name string `json:"server_name,omitempty"` // filter by server name
|
||||||
Cluster string `json:"cluster,omitempty"` // filter by cluster name
|
Cluster string `json:"cluster,omitempty"` // filter by cluster name
|
||||||
Host string `json:"host,omitempty"` // filter by host name
|
Host string `json:"host,omitempty"` // filter by host name
|
||||||
Tags []string `json:"tags,omitempty"` // filter by tags (must match all tags)
|
ExactMatch bool `json:"exact_match,omitempty"` // if the above filters should use exact matching or only "contains"
|
||||||
Domain string `json:"domain,omitempty"` // filter by JS domain
|
Tags []string `json:"tags,omitempty"` // filter by tags (must match all tags)
|
||||||
|
Domain string `json:"domain,omitempty"` // filter by JS domain
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatszEventOptions are options passed to Statsz
|
// StatszEventOptions are options passed to Statsz
|
||||||
@@ -2030,18 +2048,21 @@ type RaftzEventOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// returns true if the request does NOT apply to this server and can be ignored.
|
// returns true if the request does NOT apply to this server and can be ignored.
|
||||||
// DO NOT hold the server lock when
|
// DO NOT hold the server lock when calling this.
|
||||||
func (s *Server) filterRequest(fOpts *EventFilterOptions) bool {
|
func (s *Server) filterRequest(fOpts *EventFilterOptions) bool {
|
||||||
if fOpts.Name != _EMPTY_ && !strings.Contains(s.info.Name, fOpts.Name) {
|
if fOpts == nil {
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
if fOpts.Host != _EMPTY_ && !strings.Contains(s.info.Host, fOpts.Host) {
|
if fOpts.ExactMatch {
|
||||||
return true
|
if (fOpts.Name != _EMPTY_ && fOpts.Name != s.info.Name) ||
|
||||||
}
|
(fOpts.Host != _EMPTY_ && fOpts.Host != s.info.Host) ||
|
||||||
if fOpts.Cluster != _EMPTY_ {
|
(fOpts.Cluster != _EMPTY_ && fOpts.Cluster != s.ClusterName()) {
|
||||||
if !strings.Contains(s.ClusterName(), fOpts.Cluster) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
} else if (fOpts.Name != _EMPTY_ && !strings.Contains(s.info.Name, fOpts.Name)) ||
|
||||||
|
(fOpts.Host != _EMPTY_ && !strings.Contains(s.info.Host, fOpts.Host)) ||
|
||||||
|
(fOpts.Cluster != _EMPTY_ && !strings.Contains(s.ClusterName(), fOpts.Cluster)) {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
if len(fOpts.Tags) > 0 {
|
if len(fOpts.Tags) > 0 {
|
||||||
opts := s.getOpts()
|
opts := s.getOpts()
|
||||||
@@ -2428,37 +2449,37 @@ func (a *Account) statz() *AccountStat {
|
|||||||
|
|
||||||
a.stats.Lock()
|
a.stats.Lock()
|
||||||
received := DataStats{
|
received := DataStats{
|
||||||
dataStats: dataStats{
|
MsgBytes: MsgBytes{
|
||||||
Msgs: a.stats.inMsgs,
|
Msgs: a.stats.inMsgs,
|
||||||
Bytes: a.stats.inBytes,
|
Bytes: a.stats.inBytes,
|
||||||
},
|
},
|
||||||
Gateways: dataStats{
|
Gateways: &MsgBytes{
|
||||||
Msgs: a.stats.gw.inMsgs,
|
Msgs: a.stats.gw.inMsgs,
|
||||||
Bytes: a.stats.gw.inBytes,
|
Bytes: a.stats.gw.inBytes,
|
||||||
},
|
},
|
||||||
Routes: dataStats{
|
Routes: &MsgBytes{
|
||||||
Msgs: a.stats.rt.inMsgs,
|
Msgs: a.stats.rt.inMsgs,
|
||||||
Bytes: a.stats.rt.inBytes,
|
Bytes: a.stats.rt.inBytes,
|
||||||
},
|
},
|
||||||
Leafs: dataStats{
|
Leafs: &MsgBytes{
|
||||||
Msgs: a.stats.ln.inMsgs,
|
Msgs: a.stats.ln.inMsgs,
|
||||||
Bytes: a.stats.ln.inBytes,
|
Bytes: a.stats.ln.inBytes,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
sent := DataStats{
|
sent := DataStats{
|
||||||
dataStats: dataStats{
|
MsgBytes: MsgBytes{
|
||||||
Msgs: a.stats.outMsgs,
|
Msgs: a.stats.outMsgs,
|
||||||
Bytes: a.stats.outBytes,
|
Bytes: a.stats.outBytes,
|
||||||
},
|
},
|
||||||
Gateways: dataStats{
|
Gateways: &MsgBytes{
|
||||||
Msgs: a.stats.gw.outMsgs,
|
Msgs: a.stats.gw.outMsgs,
|
||||||
Bytes: a.stats.gw.outBytes,
|
Bytes: a.stats.gw.outBytes,
|
||||||
},
|
},
|
||||||
Routes: dataStats{
|
Routes: &MsgBytes{
|
||||||
Msgs: a.stats.rt.outMsgs,
|
Msgs: a.stats.rt.outMsgs,
|
||||||
Bytes: a.stats.rt.outBytes,
|
Bytes: a.stats.rt.outBytes,
|
||||||
},
|
},
|
||||||
Leafs: dataStats{
|
Leafs: &MsgBytes{
|
||||||
Msgs: a.stats.ln.outMsgs,
|
Msgs: a.stats.ln.outMsgs,
|
||||||
Bytes: a.stats.ln.outBytes,
|
Bytes: a.stats.ln.outBytes,
|
||||||
},
|
},
|
||||||
@@ -2504,13 +2525,11 @@ func (s *Server) accountConnectEvent(c *client) {
|
|||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gacc := s.gacc
|
|
||||||
eid := s.nextEventID()
|
eid := s.nextEventID()
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
// Ignore global account activity
|
if c.acc == nil {
|
||||||
if c.acc == nil || c.acc == gacc {
|
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2553,18 +2572,15 @@ func (s *Server) accountDisconnectEvent(c *client, now time.Time, reason string)
|
|||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gacc := s.gacc
|
|
||||||
eid := s.nextEventID()
|
eid := s.nextEventID()
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
|
|
||||||
// Ignore global account activity
|
if c.acc == nil {
|
||||||
if c.acc == nil || c.acc == gacc {
|
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m := DisconnectEventMsg{
|
m := DisconnectEventMsg{
|
||||||
TypedEvent: TypedEvent{
|
TypedEvent: TypedEvent{
|
||||||
Type: DisconnectEventMsgType,
|
Type: DisconnectEventMsgType,
|
||||||
@@ -2591,13 +2607,13 @@ func (s *Server) accountDisconnectEvent(c *client, now time.Time, reason string)
|
|||||||
MQTTClient: c.getMQTTClientID(),
|
MQTTClient: c.getMQTTClientID(),
|
||||||
},
|
},
|
||||||
Sent: DataStats{
|
Sent: DataStats{
|
||||||
dataStats: dataStats{
|
MsgBytes: MsgBytes{
|
||||||
Msgs: atomic.LoadInt64(&c.inMsgs),
|
Msgs: atomic.LoadInt64(&c.inMsgs),
|
||||||
Bytes: atomic.LoadInt64(&c.inBytes),
|
Bytes: atomic.LoadInt64(&c.inBytes),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Received: DataStats{
|
Received: DataStats{
|
||||||
dataStats: dataStats{
|
MsgBytes: MsgBytes{
|
||||||
Msgs: c.outMsgs,
|
Msgs: c.outMsgs,
|
||||||
Bytes: c.outBytes,
|
Bytes: c.outBytes,
|
||||||
},
|
},
|
||||||
@@ -2612,7 +2628,7 @@ func (s *Server) accountDisconnectEvent(c *client, now time.Time, reason string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is the system level event sent to the system account for operators.
|
// This is the system level event sent to the system account for operators.
|
||||||
func (s *Server) sendAuthErrorEvent(c *client) {
|
func (s *Server) sendAuthErrorEvent(c *client, reason string) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
if !s.eventsEnabled() {
|
if !s.eventsEnabled() {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
@@ -2649,18 +2665,18 @@ func (s *Server) sendAuthErrorEvent(c *client) {
|
|||||||
MQTTClient: c.getMQTTClientID(),
|
MQTTClient: c.getMQTTClientID(),
|
||||||
},
|
},
|
||||||
Sent: DataStats{
|
Sent: DataStats{
|
||||||
dataStats: dataStats{
|
MsgBytes: MsgBytes{
|
||||||
Msgs: c.inMsgs,
|
Msgs: c.inMsgs,
|
||||||
Bytes: c.inBytes,
|
Bytes: c.inBytes,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Received: DataStats{
|
Received: DataStats{
|
||||||
dataStats: dataStats{
|
MsgBytes: MsgBytes{
|
||||||
Msgs: c.outMsgs,
|
Msgs: c.outMsgs,
|
||||||
Bytes: c.outBytes,
|
Bytes: c.outBytes,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Reason: AuthenticationViolation.String(),
|
Reason: reason,
|
||||||
}
|
}
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
@@ -2711,13 +2727,13 @@ func (s *Server) sendAccountAuthErrorEvent(c *client, acc *Account, reason strin
|
|||||||
MQTTClient: c.getMQTTClientID(),
|
MQTTClient: c.getMQTTClientID(),
|
||||||
},
|
},
|
||||||
Sent: DataStats{
|
Sent: DataStats{
|
||||||
dataStats: dataStats{
|
MsgBytes: MsgBytes{
|
||||||
Msgs: c.inMsgs,
|
Msgs: c.inMsgs,
|
||||||
Bytes: c.inBytes,
|
Bytes: c.inBytes,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Received: DataStats{
|
Received: DataStats{
|
||||||
dataStats: dataStats{
|
MsgBytes: MsgBytes{
|
||||||
Msgs: c.outMsgs,
|
Msgs: c.outMsgs,
|
||||||
Bytes: c.outBytes,
|
Bytes: c.outBytes,
|
||||||
},
|
},
|
||||||
|
|||||||
+633
-217
File diff suppressed because it is too large
Load Diff
+16
-6
@@ -35,6 +35,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
defaultSolicitGatewaysDelay = time.Second
|
defaultSolicitGatewaysDelay = time.Second
|
||||||
defaultGatewayConnectDelay = time.Second
|
defaultGatewayConnectDelay = time.Second
|
||||||
|
defaultGatewayConnectMaxDelay = 30 * time.Second
|
||||||
defaultGatewayReconnectDelay = time.Second
|
defaultGatewayReconnectDelay = time.Second
|
||||||
defaultGatewayRecentSubExpiration = 2 * time.Second
|
defaultGatewayRecentSubExpiration = 2 * time.Second
|
||||||
defaultGatewayMaxRUnsubBeforeSwitch = 1000
|
defaultGatewayMaxRUnsubBeforeSwitch = 1000
|
||||||
@@ -59,6 +60,7 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
gatewayConnectDelay = defaultGatewayConnectDelay
|
gatewayConnectDelay = defaultGatewayConnectDelay
|
||||||
|
gatewayConnectMaxDelay = defaultGatewayConnectMaxDelay
|
||||||
gatewayReconnectDelay = defaultGatewayReconnectDelay
|
gatewayReconnectDelay = defaultGatewayReconnectDelay
|
||||||
gatewayMaxRUnsubBeforeSwitch = defaultGatewayMaxRUnsubBeforeSwitch
|
gatewayMaxRUnsubBeforeSwitch = defaultGatewayMaxRUnsubBeforeSwitch
|
||||||
gatewaySolicitDelay = int64(defaultSolicitGatewaysDelay)
|
gatewaySolicitDelay = int64(defaultSolicitGatewaysDelay)
|
||||||
@@ -703,10 +705,11 @@ func (s *Server) reconnectGateway(cfg *gatewayCfg) {
|
|||||||
// to the given Gateway. It will return once a connection has been created.
|
// to the given Gateway. It will return once a connection has been created.
|
||||||
func (s *Server) solicitGateway(cfg *gatewayCfg, firstConnect bool) {
|
func (s *Server) solicitGateway(cfg *gatewayCfg, firstConnect bool) {
|
||||||
var (
|
var (
|
||||||
opts = s.getOpts()
|
opts = s.getOpts()
|
||||||
isImplicit = cfg.isImplicit()
|
isImplicit = cfg.isImplicit()
|
||||||
attempts int
|
attemptDelay = gatewayConnectDelay
|
||||||
typeStr string
|
attempts int
|
||||||
|
typeStr string
|
||||||
)
|
)
|
||||||
if isImplicit {
|
if isImplicit {
|
||||||
typeStr = "implicit"
|
typeStr = "implicit"
|
||||||
@@ -769,7 +772,14 @@ func (s *Server) solicitGateway(cfg *gatewayCfg, firstConnect bool) {
|
|||||||
select {
|
select {
|
||||||
case <-s.quitCh:
|
case <-s.quitCh:
|
||||||
return
|
return
|
||||||
case <-time.After(gatewayConnectDelay):
|
case <-time.After(attemptDelay):
|
||||||
|
if opts.Gateway.ConnectBackoff {
|
||||||
|
// Use exponential backoff for connection attempts.
|
||||||
|
attemptDelay *= 2
|
||||||
|
if attemptDelay > gatewayConnectMaxDelay {
|
||||||
|
attemptDelay = gatewayConnectMaxDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -923,7 +933,7 @@ func (s *Server) createGateway(cfg *gatewayCfg, url *url.URL, conn net.Conn) {
|
|||||||
if tlsRequired {
|
if tlsRequired {
|
||||||
c.Debugf("TLS handshake complete")
|
c.Debugf("TLS handshake complete")
|
||||||
cs := c.nc.(*tls.Conn).ConnectionState()
|
cs := c.nc.(*tls.Conn).ConnectionState()
|
||||||
c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tlsCipher(cs.CipherSuite))
|
c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tls.CipherSuiteName(cs.CipherSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
// For outbound, we can't set the normal ping timer yet since the other
|
// For outbound, we can't set the normal ping timer yet since the other
|
||||||
|
|||||||
+75
-6
@@ -156,8 +156,8 @@ type jsAccount struct {
|
|||||||
storeDir string
|
storeDir string
|
||||||
inflight sync.Map
|
inflight sync.Map
|
||||||
streams map[string]*stream
|
streams map[string]*stream
|
||||||
templates map[string]*streamTemplate
|
templates map[string]*streamTemplate // Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
store TemplateStore
|
store TemplateStore // Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
|
|
||||||
// From server
|
// From server
|
||||||
sendq *ipQueue[*pubMsg]
|
sendq *ipQueue[*pubMsg]
|
||||||
@@ -560,7 +560,7 @@ func (s *Server) restartJetStream() error {
|
|||||||
MaxMemory: opts.JetStreamMaxMemory,
|
MaxMemory: opts.JetStreamMaxMemory,
|
||||||
MaxStore: opts.JetStreamMaxStore,
|
MaxStore: opts.JetStreamMaxStore,
|
||||||
Domain: opts.JetStreamDomain,
|
Domain: opts.JetStreamDomain,
|
||||||
Strict: opts.JetStreamStrict,
|
Strict: !opts.NoJetStreamStrict,
|
||||||
}
|
}
|
||||||
s.Noticef("Restarting JetStream")
|
s.Noticef("Restarting JetStream")
|
||||||
err := s.EnableJetStream(&cfg)
|
err := s.EnableJetStream(&cfg)
|
||||||
@@ -1451,6 +1451,61 @@ func (a *Account) EnableJetStream(limits map[string]JetStreamAccountLimits) erro
|
|||||||
mset.setCreatedTime(cfg.Created)
|
mset.setCreatedTime(cfg.Created)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Might need to recover from a partial batch write, but only if a single replica stream.
|
||||||
|
if cfg.AllowAtomicPublish && cfg.Replicas == 1 {
|
||||||
|
var (
|
||||||
|
ok bool
|
||||||
|
smv StoreMsg
|
||||||
|
batchId string
|
||||||
|
batchSeq uint64
|
||||||
|
commit bool
|
||||||
|
batchStoreDir string
|
||||||
|
store StreamStore
|
||||||
|
state StreamState
|
||||||
|
)
|
||||||
|
// Check if the last message was part of a batch.
|
||||||
|
sm, err := mset.store.LoadLastMsg(fwcs, &smv)
|
||||||
|
if err != nil || sm == nil {
|
||||||
|
goto SKIP
|
||||||
|
}
|
||||||
|
batchId = getBatchId(sm.hdr)
|
||||||
|
batchSeq, ok = getBatchSequence(sm.hdr)
|
||||||
|
commit = len(sliceHeader(JSBatchCommit, sm.hdr)) != 0
|
||||||
|
if batchId == _EMPTY_ || !ok || commit {
|
||||||
|
goto SKIP
|
||||||
|
}
|
||||||
|
// We've observed a partial batch write. Write the remainder of the batch.
|
||||||
|
batchSeq++
|
||||||
|
_, batchStoreDir = getBatchStoreDir(mset, batchId)
|
||||||
|
if _, err = os.Stat(batchStoreDir); err != nil {
|
||||||
|
s.Errorf(" Failed restoring partial batch write for stream '%s > %s' at sequence %d: %v",
|
||||||
|
mset.accName(), mset.name(), batchSeq, err)
|
||||||
|
goto SKIP
|
||||||
|
}
|
||||||
|
store, err = newBatchStore(mset, batchId)
|
||||||
|
if err != nil {
|
||||||
|
s.Errorf(" Failed restoring partial batch write for stream '%s > %s' at sequence %d: %v",
|
||||||
|
mset.accName(), mset.name(), batchSeq, err)
|
||||||
|
goto SKIP
|
||||||
|
}
|
||||||
|
store.FastState(&state)
|
||||||
|
s.Noticef(" Restoring partial batch write for stream '%s > %s' (seq %d to %d)",
|
||||||
|
mset.accName(), mset.name(), batchSeq, state.LastSeq)
|
||||||
|
// Loop through items that weren't persisted yet.
|
||||||
|
for seq := batchSeq; seq <= state.LastSeq; seq++ {
|
||||||
|
sm, err = store.LoadMsg(seq, &smv)
|
||||||
|
if err != nil || sm == nil {
|
||||||
|
s.Errorf(" Failed restoring partial batch write for stream '%s > %s' at sequence %d: %v",
|
||||||
|
mset.accName(), mset.name(), seq, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
mset.processJetStreamMsg(sm.subj, _EMPTY_, sm.hdr, sm.msg, 0, 0, nil, false, true)
|
||||||
|
}
|
||||||
|
store.Delete(true)
|
||||||
|
SKIP:
|
||||||
|
os.RemoveAll(filepath.Join(sdir, fi.Name(), batchesDir))
|
||||||
|
}
|
||||||
|
|
||||||
state := mset.state()
|
state := mset.state()
|
||||||
s.Noticef(" Restored %s messages for stream '%s > %s' in %v",
|
s.Noticef(" Restored %s messages for stream '%s > %s' in %v",
|
||||||
comma(int64(state.Msgs)), mset.accName(), mset.name(), time.Since(rt).Round(time.Millisecond))
|
comma(int64(state.Msgs)), mset.accName(), mset.name(), time.Since(rt).Round(time.Millisecond))
|
||||||
@@ -2640,7 +2695,7 @@ func (s *Server) dynJetStreamConfig(storeDir string, maxStore, maxMem int64) *Je
|
|||||||
opts := s.getOpts()
|
opts := s.getOpts()
|
||||||
|
|
||||||
// Strict mode.
|
// Strict mode.
|
||||||
jsc.Strict = opts.JetStreamStrict
|
jsc.Strict = !opts.NoJetStreamStrict
|
||||||
|
|
||||||
// Sync options.
|
// Sync options.
|
||||||
jsc.SyncInterval = opts.SyncInterval
|
jsc.SyncInterval = opts.SyncInterval
|
||||||
@@ -2687,6 +2742,7 @@ func (a *Account) checkForJetStream() (*Server, *jsAccount, error) {
|
|||||||
|
|
||||||
// StreamTemplateConfig allows a configuration to auto-create streams based on this template when a message
|
// StreamTemplateConfig allows a configuration to auto-create streams based on this template when a message
|
||||||
// is received that matches. Each new stream will use the config as the template config to create them.
|
// is received that matches. Each new stream will use the config as the template config to create them.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
type StreamTemplateConfig struct {
|
type StreamTemplateConfig struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Config *StreamConfig `json:"config"`
|
Config *StreamConfig `json:"config"`
|
||||||
@@ -2694,12 +2750,14 @@ type StreamTemplateConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StreamTemplateInfo
|
// StreamTemplateInfo
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
type StreamTemplateInfo struct {
|
type StreamTemplateInfo struct {
|
||||||
Config *StreamTemplateConfig `json:"config"`
|
Config *StreamTemplateConfig `json:"config"`
|
||||||
Streams []string `json:"streams"`
|
Streams []string `json:"streams"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// streamTemplate
|
// streamTemplate
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
type streamTemplate struct {
|
type streamTemplate struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
tc *client
|
tc *client
|
||||||
@@ -2708,6 +2766,7 @@ type streamTemplate struct {
|
|||||||
streams []string
|
streams []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (t *StreamTemplateConfig) deepCopy() *StreamTemplateConfig {
|
func (t *StreamTemplateConfig) deepCopy() *StreamTemplateConfig {
|
||||||
copy := *t
|
copy := *t
|
||||||
cfg := *t.Config
|
cfg := *t.Config
|
||||||
@@ -2716,6 +2775,7 @@ func (t *StreamTemplateConfig) deepCopy() *StreamTemplateConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// addStreamTemplate will add a stream template to this account that allows auto-creation of streams.
|
// addStreamTemplate will add a stream template to this account that allows auto-creation of streams.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (a *Account) addStreamTemplate(tc *StreamTemplateConfig) (*streamTemplate, error) {
|
func (a *Account) addStreamTemplate(tc *StreamTemplateConfig) (*streamTemplate, error) {
|
||||||
s, jsa, err := a.checkForJetStream()
|
s, jsa, err := a.checkForJetStream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2772,6 +2832,7 @@ func (a *Account) addStreamTemplate(tc *StreamTemplateConfig) (*streamTemplate,
|
|||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (t *streamTemplate) createTemplateSubscriptions() error {
|
func (t *streamTemplate) createTemplateSubscriptions() error {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return fmt.Errorf("no template")
|
return fmt.Errorf("no template")
|
||||||
@@ -2795,6 +2856,7 @@ func (t *streamTemplate) createTemplateSubscriptions() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (t *streamTemplate) processInboundTemplateMsg(_ *subscription, pc *client, acc *Account, subject, reply string, msg []byte) {
|
func (t *streamTemplate) processInboundTemplateMsg(_ *subscription, pc *client, acc *Account, subject, reply string, msg []byte) {
|
||||||
if t == nil || t.jsa == nil {
|
if t == nil || t.jsa == nil {
|
||||||
return
|
return
|
||||||
@@ -2842,6 +2904,7 @@ func (t *streamTemplate) processInboundTemplateMsg(_ *subscription, pc *client,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// lookupStreamTemplate looks up the names stream template.
|
// lookupStreamTemplate looks up the names stream template.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (a *Account) lookupStreamTemplate(name string) (*streamTemplate, error) {
|
func (a *Account) lookupStreamTemplate(name string) (*streamTemplate, error) {
|
||||||
_, jsa, err := a.checkForJetStream()
|
_, jsa, err := a.checkForJetStream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2860,6 +2923,7 @@ func (a *Account) lookupStreamTemplate(name string) (*streamTemplate, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This function will check all named streams and make sure they are valid.
|
// This function will check all named streams and make sure they are valid.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (a *Account) validateStreams(t *streamTemplate) {
|
func (a *Account) validateStreams(t *streamTemplate) {
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
var vstreams []string
|
var vstreams []string
|
||||||
@@ -2872,6 +2936,7 @@ func (a *Account) validateStreams(t *streamTemplate) {
|
|||||||
t.mu.Unlock()
|
t.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (t *streamTemplate) delete() error {
|
func (t *streamTemplate) delete() error {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return fmt.Errorf("nil stream template")
|
return fmt.Errorf("nil stream template")
|
||||||
@@ -2930,6 +2995,7 @@ func (t *streamTemplate) delete() error {
|
|||||||
return lastErr
|
return lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (a *Account) deleteStreamTemplate(name string) error {
|
func (a *Account) deleteStreamTemplate(name string) error {
|
||||||
t, err := a.lookupStreamTemplate(name)
|
t, err := a.lookupStreamTemplate(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2938,6 +3004,7 @@ func (a *Account) deleteStreamTemplate(name string) error {
|
|||||||
return t.delete()
|
return t.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (a *Account) templates() []*streamTemplate {
|
func (a *Account) templates() []*streamTemplate {
|
||||||
var ts []*streamTemplate
|
var ts []*streamTemplate
|
||||||
_, jsa, err := a.checkForJetStream()
|
_, jsa, err := a.checkForJetStream()
|
||||||
@@ -2956,6 +3023,7 @@ func (a *Account) templates() []*streamTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Will add a stream to a template, this is for recovery.
|
// Will add a stream to a template, this is for recovery.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (jsa *jsAccount) addStreamNameToTemplate(tname, mname string) error {
|
func (jsa *jsAccount) addStreamNameToTemplate(tname, mname string) error {
|
||||||
if jsa.templates == nil {
|
if jsa.templates == nil {
|
||||||
return fmt.Errorf("template not found")
|
return fmt.Errorf("template not found")
|
||||||
@@ -2973,6 +3041,7 @@ func (jsa *jsAccount) addStreamNameToTemplate(tname, mname string) error {
|
|||||||
|
|
||||||
// This will check if a template owns this stream.
|
// This will check if a template owns this stream.
|
||||||
// jsAccount lock should be held
|
// jsAccount lock should be held
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (jsa *jsAccount) checkTemplateOwnership(tname, sname string) bool {
|
func (jsa *jsAccount) checkTemplateOwnership(tname, sname string) bool {
|
||||||
if jsa.templates == nil {
|
if jsa.templates == nil {
|
||||||
return false
|
return false
|
||||||
@@ -3022,12 +3091,12 @@ func canonicalName(name string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// To throttle the out of resources errors.
|
// To throttle the out of resources errors.
|
||||||
func (s *Server) resourcesExceededError() {
|
func (s *Server) resourcesExceededError(storeType StorageType) {
|
||||||
var didAlert bool
|
var didAlert bool
|
||||||
|
|
||||||
s.rerrMu.Lock()
|
s.rerrMu.Lock()
|
||||||
if now := time.Now(); now.Sub(s.rerrLast) > 10*time.Second {
|
if now := time.Now(); now.Sub(s.rerrLast) > 10*time.Second {
|
||||||
s.Errorf("JetStream resource limits exceeded for server")
|
s.Errorf("JetStream %s resource limits exceeded for server", strings.ToLower(storeType.String()))
|
||||||
s.rerrLast = now
|
s.rerrLast = now
|
||||||
didAlert = true
|
didAlert = true
|
||||||
}
|
}
|
||||||
|
|||||||
+231
-56
@@ -49,20 +49,24 @@ const (
|
|||||||
|
|
||||||
// JSApiTemplateCreate is the endpoint to create new stream templates.
|
// JSApiTemplateCreate is the endpoint to create new stream templates.
|
||||||
// Will return JSON response.
|
// Will return JSON response.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
JSApiTemplateCreate = "$JS.API.STREAM.TEMPLATE.CREATE.*"
|
JSApiTemplateCreate = "$JS.API.STREAM.TEMPLATE.CREATE.*"
|
||||||
JSApiTemplateCreateT = "$JS.API.STREAM.TEMPLATE.CREATE.%s"
|
JSApiTemplateCreateT = "$JS.API.STREAM.TEMPLATE.CREATE.%s"
|
||||||
|
|
||||||
// JSApiTemplates is the endpoint to list all stream template names for this account.
|
// JSApiTemplates is the endpoint to list all stream template names for this account.
|
||||||
// Will return JSON response.
|
// Will return JSON response.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
JSApiTemplates = "$JS.API.STREAM.TEMPLATE.NAMES"
|
JSApiTemplates = "$JS.API.STREAM.TEMPLATE.NAMES"
|
||||||
|
|
||||||
// JSApiTemplateInfo is for obtaining general information about a named stream template.
|
// JSApiTemplateInfo is for obtaining general information about a named stream template.
|
||||||
// Will return JSON response.
|
// Will return JSON response.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
JSApiTemplateInfo = "$JS.API.STREAM.TEMPLATE.INFO.*"
|
JSApiTemplateInfo = "$JS.API.STREAM.TEMPLATE.INFO.*"
|
||||||
JSApiTemplateInfoT = "$JS.API.STREAM.TEMPLATE.INFO.%s"
|
JSApiTemplateInfoT = "$JS.API.STREAM.TEMPLATE.INFO.%s"
|
||||||
|
|
||||||
// JSApiTemplateDelete is the endpoint to delete stream templates.
|
// JSApiTemplateDelete is the endpoint to delete stream templates.
|
||||||
// Will return JSON response.
|
// Will return JSON response.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
JSApiTemplateDelete = "$JS.API.STREAM.TEMPLATE.DELETE.*"
|
JSApiTemplateDelete = "$JS.API.STREAM.TEMPLATE.DELETE.*"
|
||||||
JSApiTemplateDeleteT = "$JS.API.STREAM.TEMPLATE.DELETE.%s"
|
JSApiTemplateDeleteT = "$JS.API.STREAM.TEMPLATE.DELETE.%s"
|
||||||
|
|
||||||
@@ -305,6 +309,9 @@ const (
|
|||||||
// JSAdvisoryStreamQuorumLostPre notification that a stream and its consumers are stalled.
|
// JSAdvisoryStreamQuorumLostPre notification that a stream and its consumers are stalled.
|
||||||
JSAdvisoryStreamQuorumLostPre = "$JS.EVENT.ADVISORY.STREAM.QUORUM_LOST"
|
JSAdvisoryStreamQuorumLostPre = "$JS.EVENT.ADVISORY.STREAM.QUORUM_LOST"
|
||||||
|
|
||||||
|
// JSAdvisoryStreamBatchAbandonedPre notification that a stream's batch was abandoned.
|
||||||
|
JSAdvisoryStreamBatchAbandonedPre = "$JS.EVENT.ADVISORY.STREAM.BATCH_ABANDONED"
|
||||||
|
|
||||||
// JSAdvisoryConsumerLeaderElectedPre notification that a replicated consumer has elected a leader.
|
// JSAdvisoryConsumerLeaderElectedPre notification that a replicated consumer has elected a leader.
|
||||||
JSAdvisoryConsumerLeaderElectedPre = "$JS.EVENT.ADVISORY.CONSUMER.LEADER_ELECTED"
|
JSAdvisoryConsumerLeaderElectedPre = "$JS.EVENT.ADVISORY.CONSUMER.LEADER_ELECTED"
|
||||||
|
|
||||||
@@ -325,6 +332,12 @@ const (
|
|||||||
JSAuditAdvisory = "$JS.EVENT.ADVISORY.API"
|
JSAuditAdvisory = "$JS.EVENT.ADVISORY.API"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Headers used in $JS.API.> requests.
|
||||||
|
const (
|
||||||
|
// JSRequiredApiLevel requires the API level of the responding server to have the specified minimum value.
|
||||||
|
JSRequiredApiLevel = "Nats-Required-Api-Level"
|
||||||
|
)
|
||||||
|
|
||||||
var denyAllClientJs = []string{jsAllAPI, "$KV.>", "$OBJ.>"}
|
var denyAllClientJs = []string{jsAllAPI, "$KV.>", "$OBJ.>"}
|
||||||
var denyAllJs = []string{jscAllSubj, raftAllSubj, jsAllAPI, "$KV.>", "$OBJ.>"}
|
var denyAllJs = []string{jscAllSubj, raftAllSubj, jsAllAPI, "$KV.>", "$OBJ.>"}
|
||||||
|
|
||||||
@@ -672,7 +685,7 @@ type JSApiMsgGetRequest struct {
|
|||||||
LastFor string `json:"last_by_subj,omitempty"`
|
LastFor string `json:"last_by_subj,omitempty"`
|
||||||
NextFor string `json:"next_by_subj,omitempty"`
|
NextFor string `json:"next_by_subj,omitempty"`
|
||||||
|
|
||||||
// Batch support. Used to request more then one msg at a time.
|
// Batch support. Used to request more than one msg at a time.
|
||||||
// Can be used with simple starting seq, but also NextFor with wildcards.
|
// Can be used with simple starting seq, but also NextFor with wildcards.
|
||||||
Batch int `json:"batch,omitempty"`
|
Batch int `json:"batch,omitempty"`
|
||||||
// This will make sure we limit how much data we blast out. If not set we will
|
// This will make sure we limit how much data we blast out. If not set we will
|
||||||
@@ -687,6 +700,8 @@ type JSApiMsgGetRequest struct {
|
|||||||
UpToSeq uint64 `json:"up_to_seq,omitempty"`
|
UpToSeq uint64 `json:"up_to_seq,omitempty"`
|
||||||
// Only return messages up to this time.
|
// Only return messages up to this time.
|
||||||
UpToTime *time.Time `json:"up_to_time,omitempty"`
|
UpToTime *time.Time `json:"up_to_time,omitempty"`
|
||||||
|
// Only return the message payload, excluding headers if present.
|
||||||
|
NoHeaders bool `json:"no_hdr,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type JSApiMsgGetResponse struct {
|
type JSApiMsgGetResponse struct {
|
||||||
@@ -766,44 +781,52 @@ type JSApiConsumerGetNextRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// JSApiStreamTemplateCreateResponse for creating templates.
|
// JSApiStreamTemplateCreateResponse for creating templates.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
type JSApiStreamTemplateCreateResponse struct {
|
type JSApiStreamTemplateCreateResponse struct {
|
||||||
ApiResponse
|
ApiResponse
|
||||||
*StreamTemplateInfo
|
*StreamTemplateInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
const JSApiStreamTemplateCreateResponseType = "io.nats.jetstream.api.v1.stream_template_create_response"
|
const JSApiStreamTemplateCreateResponseType = "io.nats.jetstream.api.v1.stream_template_create_response"
|
||||||
|
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
type JSApiStreamTemplateDeleteResponse struct {
|
type JSApiStreamTemplateDeleteResponse struct {
|
||||||
ApiResponse
|
ApiResponse
|
||||||
Success bool `json:"success,omitempty"`
|
Success bool `json:"success,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
const JSApiStreamTemplateDeleteResponseType = "io.nats.jetstream.api.v1.stream_template_delete_response"
|
const JSApiStreamTemplateDeleteResponseType = "io.nats.jetstream.api.v1.stream_template_delete_response"
|
||||||
|
|
||||||
// JSApiStreamTemplateInfoResponse for information about stream templates.
|
// JSApiStreamTemplateInfoResponse for information about stream templates.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
type JSApiStreamTemplateInfoResponse struct {
|
type JSApiStreamTemplateInfoResponse struct {
|
||||||
ApiResponse
|
ApiResponse
|
||||||
*StreamTemplateInfo
|
*StreamTemplateInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
const JSApiStreamTemplateInfoResponseType = "io.nats.jetstream.api.v1.stream_template_info_response"
|
const JSApiStreamTemplateInfoResponseType = "io.nats.jetstream.api.v1.stream_template_info_response"
|
||||||
|
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
type JSApiStreamTemplatesRequest struct {
|
type JSApiStreamTemplatesRequest struct {
|
||||||
ApiPagedRequest
|
ApiPagedRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSApiStreamTemplateNamesResponse list of templates
|
// JSApiStreamTemplateNamesResponse list of templates
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
type JSApiStreamTemplateNamesResponse struct {
|
type JSApiStreamTemplateNamesResponse struct {
|
||||||
ApiResponse
|
ApiResponse
|
||||||
ApiPaged
|
ApiPaged
|
||||||
Templates []string `json:"streams"`
|
Templates []string `json:"streams"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
const JSApiStreamTemplateNamesResponseType = "io.nats.jetstream.api.v1.stream_template_names_response"
|
const JSApiStreamTemplateNamesResponseType = "io.nats.jetstream.api.v1.stream_template_names_response"
|
||||||
|
|
||||||
// Structure that holds state for a JetStream API request that is processed
|
// Structure that holds state for a JetStream API request that is processed
|
||||||
// in a separate long-lived go routine. This is to avoid possibly blocking
|
// in a separate long-lived go routine. This is to avoid blocking connections.
|
||||||
// ROUTE and GATEWAY connections.
|
|
||||||
type jsAPIRoutedReq struct {
|
type jsAPIRoutedReq struct {
|
||||||
jsub *subscription
|
jsub *subscription
|
||||||
sub *subscription
|
sub *subscription
|
||||||
@@ -872,17 +895,6 @@ func (js *jetStream) apiDispatch(sub *subscription, c *client, acc *Account, sub
|
|||||||
}
|
}
|
||||||
jsub := rr.psubs[0]
|
jsub := rr.psubs[0]
|
||||||
|
|
||||||
// If this is directly from a client connection ok to do in place.
|
|
||||||
if c.kind != ROUTER && c.kind != GATEWAY && c.kind != LEAF {
|
|
||||||
start := time.Now()
|
|
||||||
jsub.icb(sub, c, acc, subject, reply, rmsg)
|
|
||||||
if dur := time.Since(start); dur >= readLoopReportThreshold {
|
|
||||||
s.Warnf("Internal subscription on %q took too long: %v", subject, dur)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are here we have received this request over a non-client connection.
|
|
||||||
// We need to make sure not to block. We will send the request to a long-lived
|
// We need to make sure not to block. We will send the request to a long-lived
|
||||||
// pool of go routines.
|
// pool of go routines.
|
||||||
|
|
||||||
@@ -1277,13 +1289,18 @@ func (s *Server) jsAccountInfoRequest(sub *subscription, c *client, _ *Account,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiAccountInfoResponse{ApiResponse: ApiResponse{Type: JSApiAccountInfoResponseType}}
|
var resp = JSApiAccountInfoResponse{ApiResponse: ApiResponse{Type: JSApiAccountInfoResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if we should proceed here when we are in clustered mode.
|
// Determine if we should proceed here when we are in clustered mode.
|
||||||
if s.JetStreamIsClustered() {
|
if s.JetStreamIsClustered() {
|
||||||
@@ -1333,17 +1350,23 @@ func consumerNameFromSubject(subject string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Request to create a new template.
|
// Request to create a new template.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (s *Server) jsTemplateCreateRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) {
|
func (s *Server) jsTemplateCreateRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiStreamTemplateCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamTemplateCreateResponseType}}
|
var resp = JSApiStreamTemplateCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamTemplateCreateResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
if !acc.JetStreamEnabled() {
|
if !acc.JetStreamEnabled() {
|
||||||
resp.Error = NewJSNotEnabledForAccountError()
|
resp.Error = NewJSNotEnabledForAccountError()
|
||||||
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
@@ -1388,17 +1411,23 @@ func (s *Server) jsTemplateCreateRequest(sub *subscription, c *client, _ *Accoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Request for the list of all template names.
|
// Request for the list of all template names.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (s *Server) jsTemplateNamesRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) {
|
func (s *Server) jsTemplateNamesRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiStreamTemplateNamesResponse{ApiResponse: ApiResponse{Type: JSApiStreamTemplateNamesResponseType}}
|
var resp = JSApiStreamTemplateNamesResponse{ApiResponse: ApiResponse{Type: JSApiStreamTemplateNamesResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
if !acc.JetStreamEnabled() {
|
if !acc.JetStreamEnabled() {
|
||||||
resp.Error = NewJSNotEnabledForAccountError()
|
resp.Error = NewJSNotEnabledForAccountError()
|
||||||
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
@@ -1452,17 +1481,23 @@ func (s *Server) jsTemplateNamesRequest(sub *subscription, c *client, _ *Account
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Request for information about a stream template.
|
// Request for information about a stream template.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (s *Server) jsTemplateInfoRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) {
|
func (s *Server) jsTemplateInfoRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiStreamTemplateInfoResponse{ApiResponse: ApiResponse{Type: JSApiStreamTemplateInfoResponseType}}
|
var resp = JSApiStreamTemplateInfoResponse{ApiResponse: ApiResponse{Type: JSApiStreamTemplateInfoResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
if !acc.JetStreamEnabled() {
|
if !acc.JetStreamEnabled() {
|
||||||
resp.Error = NewJSNotEnabledForAccountError()
|
resp.Error = NewJSNotEnabledForAccountError()
|
||||||
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
@@ -1493,17 +1528,23 @@ func (s *Server) jsTemplateInfoRequest(sub *subscription, c *client, _ *Account,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Request to delete a stream template.
|
// Request to delete a stream template.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
func (s *Server) jsTemplateDeleteRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) {
|
func (s *Server) jsTemplateDeleteRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiStreamTemplateDeleteResponse{ApiResponse: ApiResponse{Type: JSApiStreamTemplateDeleteResponseType}}
|
var resp = JSApiStreamTemplateDeleteResponse{ApiResponse: ApiResponse{Type: JSApiStreamTemplateDeleteResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
if !acc.JetStreamEnabled() {
|
if !acc.JetStreamEnabled() {
|
||||||
resp.Error = NewJSNotEnabledForAccountError()
|
resp.Error = NewJSNotEnabledForAccountError()
|
||||||
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
@@ -1564,13 +1605,18 @@ func (s *Server) jsStreamCreateRequest(sub *subscription, c *client, _ *Account,
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiStreamCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}}
|
var resp = JSApiStreamCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if we should proceed here when we are in clustered mode.
|
// Determine if we should proceed here when we are in clustered mode.
|
||||||
if s.JetStreamIsClustered() {
|
if s.JetStreamIsClustered() {
|
||||||
@@ -1676,13 +1722,18 @@ func (s *Server) jsStreamUpdateRequest(sub *subscription, c *client, _ *Account,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiStreamUpdateResponse{ApiResponse: ApiResponse{Type: JSApiStreamUpdateResponseType}}
|
var resp = JSApiStreamUpdateResponse{ApiResponse: ApiResponse{Type: JSApiStreamUpdateResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if we should proceed here when we are in clustered mode.
|
// Determine if we should proceed here when we are in clustered mode.
|
||||||
if s.JetStreamIsClustered() {
|
if s.JetStreamIsClustered() {
|
||||||
@@ -1731,8 +1782,7 @@ func (s *Server) jsStreamUpdateRequest(sub *subscription, c *client, _ *Account,
|
|||||||
|
|
||||||
// Handle clustered version here.
|
// Handle clustered version here.
|
||||||
if s.JetStreamIsClustered() {
|
if s.JetStreamIsClustered() {
|
||||||
// Always do in separate Go routine.
|
s.jsClusteredStreamUpdateRequest(ci, acc, subject, reply, copyBytes(rmsg), &cfg, nil, ncfg.Pedantic)
|
||||||
go s.jsClusteredStreamUpdateRequest(ci, acc, subject, reply, copyBytes(rmsg), &cfg, nil, ncfg.Pedantic)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1775,13 +1825,18 @@ func (s *Server) jsStreamNamesRequest(sub *subscription, c *client, _ *Account,
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiStreamNamesResponse{ApiResponse: ApiResponse{Type: JSApiStreamNamesResponseType}}
|
var resp = JSApiStreamNamesResponse{ApiResponse: ApiResponse{Type: JSApiStreamNamesResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if we should proceed here when we are in clustered mode.
|
// Determine if we should proceed here when we are in clustered mode.
|
||||||
if s.JetStreamIsClustered() {
|
if s.JetStreamIsClustered() {
|
||||||
@@ -1902,7 +1957,7 @@ func (s *Server) jsStreamListRequest(sub *subscription, c *client, _ *Account, s
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -1912,6 +1967,11 @@ func (s *Server) jsStreamListRequest(sub *subscription, c *client, _ *Account, s
|
|||||||
ApiResponse: ApiResponse{Type: JSApiStreamListResponseType},
|
ApiResponse: ApiResponse{Type: JSApiStreamListResponseType},
|
||||||
Streams: []*StreamInfo{},
|
Streams: []*StreamInfo{},
|
||||||
}
|
}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if we should proceed here when we are in clustered mode.
|
// Determine if we should proceed here when we are in clustered mode.
|
||||||
if s.JetStreamIsClustered() {
|
if s.JetStreamIsClustered() {
|
||||||
@@ -2030,6 +2090,11 @@ func (s *Server) jsStreamInfoRequest(sub *subscription, c *client, a *Account, s
|
|||||||
if rt := getHeader(JSResponseType, hdr); len(rt) > 0 && string(rt) == jsCreateResponse {
|
if rt := getHeader(JSResponseType, hdr); len(rt) > 0 && string(rt) == jsCreateResponse {
|
||||||
resp.ApiResponse.Type = JSApiStreamCreateResponseType
|
resp.ApiResponse.Type = JSApiStreamCreateResponseType
|
||||||
}
|
}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var clusterWideConsCount int
|
var clusterWideConsCount int
|
||||||
|
|
||||||
@@ -2234,7 +2299,7 @@ func (s *Server) jsStreamLeaderStepDownRequest(sub *subscription, c *client, _ *
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -2244,6 +2309,11 @@ func (s *Server) jsStreamLeaderStepDownRequest(sub *subscription, c *client, _ *
|
|||||||
name := tokenAt(subject, 6)
|
name := tokenAt(subject, 6)
|
||||||
|
|
||||||
var resp = JSApiStreamLeaderStepDownResponse{ApiResponse: ApiResponse{Type: JSApiStreamLeaderStepDownResponseType}}
|
var resp = JSApiStreamLeaderStepDownResponse{ApiResponse: ApiResponse{Type: JSApiStreamLeaderStepDownResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If we are not in clustered mode this is a failed request.
|
// If we are not in clustered mode this is a failed request.
|
||||||
if !s.JetStreamIsClustered() {
|
if !s.JetStreamIsClustered() {
|
||||||
@@ -2344,13 +2414,18 @@ func (s *Server) jsConsumerLeaderStepDownRequest(sub *subscription, c *client, _
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiConsumerLeaderStepDownResponse{ApiResponse: ApiResponse{Type: JSApiConsumerLeaderStepDownResponseType}}
|
var resp = JSApiConsumerLeaderStepDownResponse{ApiResponse: ApiResponse{Type: JSApiConsumerLeaderStepDownResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If we are not in clustered mode this is a failed request.
|
// If we are not in clustered mode this is a failed request.
|
||||||
if !s.JetStreamIsClustered() {
|
if !s.JetStreamIsClustered() {
|
||||||
@@ -2462,7 +2537,7 @@ func (s *Server) jsStreamRemovePeerRequest(sub *subscription, c *client, _ *Acco
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -2472,6 +2547,11 @@ func (s *Server) jsStreamRemovePeerRequest(sub *subscription, c *client, _ *Acco
|
|||||||
name := tokenAt(subject, 6)
|
name := tokenAt(subject, 6)
|
||||||
|
|
||||||
var resp = JSApiStreamRemovePeerResponse{ApiResponse: ApiResponse{Type: JSApiStreamRemovePeerResponseType}}
|
var resp = JSApiStreamRemovePeerResponse{ApiResponse: ApiResponse{Type: JSApiStreamRemovePeerResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If we are not in clustered mode this is a failed request.
|
// If we are not in clustered mode this is a failed request.
|
||||||
if !s.JetStreamIsClustered() {
|
if !s.JetStreamIsClustered() {
|
||||||
@@ -2565,7 +2645,7 @@ func (s *Server) jsLeaderServerRemoveRequest(sub *subscription, c *client, _ *Ac
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -2589,6 +2669,11 @@ func (s *Server) jsLeaderServerRemoveRequest(sub *subscription, c *client, _ *Ac
|
|||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiMetaServerRemoveResponse{ApiResponse: ApiResponse{Type: JSApiMetaServerRemoveResponseType}}
|
var resp = JSApiMetaServerRemoveResponse{ApiResponse: ApiResponse{Type: JSApiMetaServerRemoveResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if isEmptyRequest(msg) {
|
if isEmptyRequest(msg) {
|
||||||
resp.Error = NewJSBadRequestError()
|
resp.Error = NewJSBadRequestError()
|
||||||
@@ -2674,7 +2759,7 @@ func (s *Server) jsLeaderServerStreamMoveRequest(sub *subscription, c *client, _
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -2702,6 +2787,11 @@ func (s *Server) jsLeaderServerStreamMoveRequest(sub *subscription, c *client, _
|
|||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiStreamUpdateResponse{ApiResponse: ApiResponse{Type: JSApiStreamUpdateResponseType}}
|
var resp = JSApiStreamUpdateResponse{ApiResponse: ApiResponse{Type: JSApiStreamUpdateResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var req JSApiMetaServerStreamMoveRequest
|
var req JSApiMetaServerStreamMoveRequest
|
||||||
if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil {
|
if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil {
|
||||||
@@ -2833,7 +2923,7 @@ func (s *Server) jsLeaderServerStreamCancelMoveRequest(sub *subscription, c *cli
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -2854,6 +2944,11 @@ func (s *Server) jsLeaderServerStreamCancelMoveRequest(sub *subscription, c *cli
|
|||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiStreamUpdateResponse{ApiResponse: ApiResponse{Type: JSApiStreamUpdateResponseType}}
|
var resp = JSApiStreamUpdateResponse{ApiResponse: ApiResponse{Type: JSApiStreamUpdateResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
accName := tokenAt(subject, 6)
|
accName := tokenAt(subject, 6)
|
||||||
streamName := tokenAt(subject, 7)
|
streamName := tokenAt(subject, 7)
|
||||||
@@ -2943,7 +3038,7 @@ func (s *Server) jsLeaderAccountPurgeRequest(sub *subscription, c *client, _ *Ac
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -2960,6 +3055,11 @@ func (s *Server) jsLeaderAccountPurgeRequest(sub *subscription, c *client, _ *Ac
|
|||||||
accName := tokenAt(subject, 5)
|
accName := tokenAt(subject, 5)
|
||||||
|
|
||||||
var resp = JSApiAccountPurgeResponse{ApiResponse: ApiResponse{Type: JSApiAccountPurgeResponseType}}
|
var resp = JSApiAccountPurgeResponse{ApiResponse: ApiResponse{Type: JSApiAccountPurgeResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !s.JetStreamIsClustered() {
|
if !s.JetStreamIsClustered() {
|
||||||
var streams []*stream
|
var streams []*stream
|
||||||
@@ -3030,7 +3130,7 @@ func (s *Server) jsLeaderStepDownRequest(sub *subscription, c *client, _ *Accoun
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -3058,6 +3158,11 @@ func (s *Server) jsLeaderStepDownRequest(sub *subscription, c *client, _ *Accoun
|
|||||||
|
|
||||||
var preferredLeader string
|
var preferredLeader string
|
||||||
var resp = JSApiLeaderStepDownResponse{ApiResponse: ApiResponse{Type: JSApiLeaderStepDownResponseType}}
|
var resp = JSApiLeaderStepDownResponse{ApiResponse: ApiResponse{Type: JSApiLeaderStepDownResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if isJSONObjectOrArray(msg) {
|
if isJSONObjectOrArray(msg) {
|
||||||
var req JSApiLeaderStepdownRequest
|
var req JSApiLeaderStepdownRequest
|
||||||
@@ -3203,13 +3308,18 @@ func (s *Server) jsStreamDeleteRequest(sub *subscription, c *client, _ *Account,
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiStreamDeleteResponse{ApiResponse: ApiResponse{Type: JSApiStreamDeleteResponseType}}
|
var resp = JSApiStreamDeleteResponse{ApiResponse: ApiResponse{Type: JSApiStreamDeleteResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if we should proceed here when we are in clustered mode.
|
// Determine if we should proceed here when we are in clustered mode.
|
||||||
if s.JetStreamIsClustered() {
|
if s.JetStreamIsClustered() {
|
||||||
@@ -3271,7 +3381,7 @@ func (s *Server) jsMsgDeleteRequest(sub *subscription, c *client, _ *Account, su
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -3280,6 +3390,11 @@ func (s *Server) jsMsgDeleteRequest(sub *subscription, c *client, _ *Account, su
|
|||||||
stream := tokenAt(subject, 6)
|
stream := tokenAt(subject, 6)
|
||||||
|
|
||||||
var resp = JSApiMsgDeleteResponse{ApiResponse: ApiResponse{Type: JSApiMsgDeleteResponseType}}
|
var resp = JSApiMsgDeleteResponse{ApiResponse: ApiResponse{Type: JSApiMsgDeleteResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If we are in clustered mode we need to be the stream leader to proceed.
|
// If we are in clustered mode we need to be the stream leader to proceed.
|
||||||
if s.JetStreamIsClustered() {
|
if s.JetStreamIsClustered() {
|
||||||
@@ -3390,7 +3505,7 @@ func (s *Server) jsMsgGetRequest(sub *subscription, c *client, _ *Account, subje
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -3399,6 +3514,11 @@ func (s *Server) jsMsgGetRequest(sub *subscription, c *client, _ *Account, subje
|
|||||||
stream := tokenAt(subject, 6)
|
stream := tokenAt(subject, 6)
|
||||||
|
|
||||||
var resp = JSApiMsgGetResponse{ApiResponse: ApiResponse{Type: JSApiMsgGetResponseType}}
|
var resp = JSApiMsgGetResponse{ApiResponse: ApiResponse{Type: JSApiMsgGetResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If we are in clustered mode we need to be the stream leader to proceed.
|
// If we are in clustered mode we need to be the stream leader to proceed.
|
||||||
if s.JetStreamIsClustered() {
|
if s.JetStreamIsClustered() {
|
||||||
@@ -3499,6 +3619,10 @@ func (s *Server) jsMsgGetRequest(sub *subscription, c *client, _ *Account, subje
|
|||||||
var svp StoreMsg
|
var svp StoreMsg
|
||||||
var sm *StoreMsg
|
var sm *StoreMsg
|
||||||
|
|
||||||
|
// Ensure this read request is isolated and doesn't interleave with writes.
|
||||||
|
mset.mu.RLock()
|
||||||
|
defer mset.mu.RUnlock()
|
||||||
|
|
||||||
// If AsOfTime is set, perform this first to get the sequence.
|
// If AsOfTime is set, perform this first to get the sequence.
|
||||||
var seq uint64
|
var seq uint64
|
||||||
if req.StartTime != nil {
|
if req.StartTime != nil {
|
||||||
@@ -3522,10 +3646,12 @@ func (s *Server) jsMsgGetRequest(sub *subscription, c *client, _ *Account, subje
|
|||||||
resp.Message = &StoredMsg{
|
resp.Message = &StoredMsg{
|
||||||
Subject: sm.subj,
|
Subject: sm.subj,
|
||||||
Sequence: sm.seq,
|
Sequence: sm.seq,
|
||||||
Header: sm.hdr,
|
|
||||||
Data: sm.msg,
|
Data: sm.msg,
|
||||||
Time: time.Unix(0, sm.ts).UTC(),
|
Time: time.Unix(0, sm.ts).UTC(),
|
||||||
}
|
}
|
||||||
|
if !req.NoHeaders {
|
||||||
|
resp.Message.Header = sm.hdr
|
||||||
|
}
|
||||||
|
|
||||||
// Don't send response through API layer for this call.
|
// Don't send response through API layer for this call.
|
||||||
s.sendInternalAccountMsg(nil, reply, s.jsonResponse(resp))
|
s.sendInternalAccountMsg(nil, reply, s.jsonResponse(resp))
|
||||||
@@ -3536,7 +3662,7 @@ func (s *Server) jsConsumerUnpinRequest(sub *subscription, c *client, _ *Account
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -3547,6 +3673,11 @@ func (s *Server) jsConsumerUnpinRequest(sub *subscription, c *client, _ *Account
|
|||||||
|
|
||||||
var req JSApiConsumerUnpinRequest
|
var req JSApiConsumerUnpinRequest
|
||||||
var resp = JSApiConsumerUnpinResponse{ApiResponse: ApiResponse{Type: JSApiConsumerUnpinResponseType}}
|
var resp = JSApiConsumerUnpinResponse{ApiResponse: ApiResponse{Type: JSApiConsumerUnpinResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(msg, &req); err != nil {
|
if err := json.Unmarshal(msg, &req); err != nil {
|
||||||
resp.Error = NewJSInvalidJSONError(err)
|
resp.Error = NewJSInvalidJSONError(err)
|
||||||
@@ -3670,7 +3801,7 @@ func (s *Server) jsStreamPurgeRequest(sub *subscription, c *client, _ *Account,
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -3679,6 +3810,11 @@ func (s *Server) jsStreamPurgeRequest(sub *subscription, c *client, _ *Account,
|
|||||||
stream := streamNameFromSubject(subject)
|
stream := streamNameFromSubject(subject)
|
||||||
|
|
||||||
var resp = JSApiStreamPurgeResponse{ApiResponse: ApiResponse{Type: JSApiStreamPurgeResponseType}}
|
var resp = JSApiStreamPurgeResponse{ApiResponse: ApiResponse{Type: JSApiStreamPurgeResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If we are in clustered mode we need to be the stream leader to proceed.
|
// If we are in clustered mode we need to be the stream leader to proceed.
|
||||||
if s.JetStreamIsClustered() {
|
if s.JetStreamIsClustered() {
|
||||||
@@ -3812,13 +3948,18 @@ func (s *Server) jsStreamRestoreRequest(sub *subscription, c *client, _ *Account
|
|||||||
if c == nil || !s.JetStreamIsLeader() {
|
if c == nil || !s.JetStreamIsLeader() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiStreamRestoreResponse{ApiResponse: ApiResponse{Type: JSApiStreamRestoreResponseType}}
|
var resp = JSApiStreamRestoreResponse{ApiResponse: ApiResponse{Type: JSApiStreamRestoreResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
if !acc.JetStreamEnabled() {
|
if !acc.JetStreamEnabled() {
|
||||||
resp.Error = NewJSNotEnabledForAccountError()
|
resp.Error = NewJSNotEnabledForAccountError()
|
||||||
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
@@ -3964,7 +4105,7 @@ func (s *Server) processStreamRestore(ci *ClientInfo, acc *Account, cfg *StreamC
|
|||||||
// TODO(dlc) - We could check apriori and cancel initial request if we know it won't fit.
|
// TODO(dlc) - We could check apriori and cancel initial request if we know it won't fit.
|
||||||
total += len(msg)
|
total += len(msg)
|
||||||
if js.wouldExceedLimits(FileStorage, total) {
|
if js.wouldExceedLimits(FileStorage, total) {
|
||||||
s.resourcesExceededError()
|
s.resourcesExceededError(FileStorage)
|
||||||
resultCh <- result{NewJSInsufficientResourcesError(), reply}
|
resultCh <- result{NewJSInsufficientResourcesError(), reply}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4092,7 +4233,7 @@ func (s *Server) jsStreamSnapshotRequest(sub *subscription, c *client, _ *Accoun
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -4107,6 +4248,11 @@ func (s *Server) jsStreamSnapshotRequest(sub *subscription, c *client, _ *Accoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiStreamSnapshotResponse{ApiResponse: ApiResponse{Type: JSApiStreamSnapshotResponseType}}
|
var resp = JSApiStreamSnapshotResponse{ApiResponse: ApiResponse{Type: JSApiStreamSnapshotResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
if !acc.JetStreamEnabled() {
|
if !acc.JetStreamEnabled() {
|
||||||
resp.Error = NewJSNotEnabledForAccountError()
|
resp.Error = NewJSNotEnabledForAccountError()
|
||||||
s.sendAPIErrResponse(ci, acc, subject, reply, smsg, s.jsonResponse(&resp))
|
s.sendAPIErrResponse(ci, acc, subject, reply, smsg, s.jsonResponse(&resp))
|
||||||
@@ -4328,13 +4474,18 @@ func (s *Server) jsConsumerCreateRequest(sub *subscription, c *client, a *Accoun
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiConsumerCreateResponseType}}
|
var resp = JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiConsumerCreateResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var req CreateConsumerRequest
|
var req CreateConsumerRequest
|
||||||
if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil {
|
if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil {
|
||||||
@@ -4479,14 +4630,7 @@ func (s *Server) jsConsumerCreateRequest(sub *subscription, c *client, a *Accoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isClustered && !req.Config.Direct {
|
if isClustered && !req.Config.Direct {
|
||||||
// If we are inline with client, we still may need to do a callout for consumer info
|
s.jsClusteredConsumerRequest(ci, acc, subject, reply, rmsg, req.Stream, &req.Config, req.Action, req.Pedantic)
|
||||||
// during this call, so place in Go routine to not block client.
|
|
||||||
// Router and Gateway API calls already in separate context.
|
|
||||||
if c.kind != ROUTER && c.kind != GATEWAY {
|
|
||||||
go s.jsClusteredConsumerRequest(ci, acc, subject, reply, rmsg, req.Stream, &req.Config, req.Action, req.Pedantic)
|
|
||||||
} else {
|
|
||||||
s.jsClusteredConsumerRequest(ci, acc, subject, reply, rmsg, req.Stream, &req.Config, req.Action, req.Pedantic)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4552,7 +4696,7 @@ func (s *Server) jsConsumerNamesRequest(sub *subscription, c *client, _ *Account
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -4562,6 +4706,11 @@ func (s *Server) jsConsumerNamesRequest(sub *subscription, c *client, _ *Account
|
|||||||
ApiResponse: ApiResponse{Type: JSApiConsumerNamesResponseType},
|
ApiResponse: ApiResponse{Type: JSApiConsumerNamesResponseType},
|
||||||
Consumers: []string{},
|
Consumers: []string{},
|
||||||
}
|
}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if we should proceed here when we are in clustered mode.
|
// Determine if we should proceed here when we are in clustered mode.
|
||||||
if s.JetStreamIsClustered() {
|
if s.JetStreamIsClustered() {
|
||||||
@@ -4674,7 +4823,7 @@ func (s *Server) jsConsumerListRequest(sub *subscription, c *client, _ *Account,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -4684,6 +4833,11 @@ func (s *Server) jsConsumerListRequest(sub *subscription, c *client, _ *Account,
|
|||||||
ApiResponse: ApiResponse{Type: JSApiConsumerListResponseType},
|
ApiResponse: ApiResponse{Type: JSApiConsumerListResponseType},
|
||||||
Consumers: []*ConsumerInfo{},
|
Consumers: []*ConsumerInfo{},
|
||||||
}
|
}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if we should proceed here when we are in clustered mode.
|
// Determine if we should proceed here when we are in clustered mode.
|
||||||
if s.JetStreamIsClustered() {
|
if s.JetStreamIsClustered() {
|
||||||
@@ -4702,6 +4856,12 @@ func (s *Server) jsConsumerListRequest(sub *subscription, c *client, _ *Account,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSClusterNotAvailError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if hasJS, doErr := acc.checkJetStream(); !hasJS {
|
if hasJS, doErr := acc.checkJetStream(); !hasJS {
|
||||||
if doErr {
|
if doErr {
|
||||||
resp.Error = NewJSNotEnabledForAccountError()
|
resp.Error = NewJSNotEnabledForAccountError()
|
||||||
@@ -4777,7 +4937,7 @@ func (s *Server) jsConsumerInfoRequest(sub *subscription, c *client, _ *Account,
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -4787,6 +4947,11 @@ func (s *Server) jsConsumerInfoRequest(sub *subscription, c *client, _ *Account,
|
|||||||
consumerName := consumerNameFromSubject(subject)
|
consumerName := consumerNameFromSubject(subject)
|
||||||
|
|
||||||
var resp = JSApiConsumerInfoResponse{ApiResponse: ApiResponse{Type: JSApiConsumerInfoResponseType}}
|
var resp = JSApiConsumerInfoResponse{ApiResponse: ApiResponse{Type: JSApiConsumerInfoResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !isEmptyRequest(msg) {
|
if !isEmptyRequest(msg) {
|
||||||
resp.Error = NewJSNotEmptyRequestError()
|
resp.Error = NewJSNotEmptyRequestError()
|
||||||
@@ -4973,13 +5138,18 @@ func (s *Server) jsConsumerDeleteRequest(sub *subscription, c *client, _ *Accoun
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp = JSApiConsumerDeleteResponse{ApiResponse: ApiResponse{Type: JSApiConsumerDeleteResponseType}}
|
var resp = JSApiConsumerDeleteResponse{ApiResponse: ApiResponse{Type: JSApiConsumerDeleteResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if we should proceed here when we are in clustered mode.
|
// Determine if we should proceed here when we are in clustered mode.
|
||||||
if s.JetStreamIsClustered() {
|
if s.JetStreamIsClustered() {
|
||||||
@@ -5045,7 +5215,7 @@ func (s *Server) jsConsumerPauseRequest(sub *subscription, c *client, _ *Account
|
|||||||
if c == nil || !s.JetStreamEnabled() {
|
if c == nil || !s.JetStreamEnabled() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ci, acc, _, msg, err := s.getRequestInfo(c, rmsg)
|
ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Warnf(badAPIRequestT, msg)
|
s.Warnf(badAPIRequestT, msg)
|
||||||
return
|
return
|
||||||
@@ -5053,6 +5223,11 @@ func (s *Server) jsConsumerPauseRequest(sub *subscription, c *client, _ *Account
|
|||||||
|
|
||||||
var req JSApiConsumerPauseRequest
|
var req JSApiConsumerPauseRequest
|
||||||
var resp = JSApiConsumerPauseResponse{ApiResponse: ApiResponse{Type: JSApiConsumerPauseResponseType}}
|
var resp = JSApiConsumerPauseResponse{ApiResponse: ApiResponse{Type: JSApiConsumerPauseResponseType}}
|
||||||
|
if errorOnRequiredApiLevel(hdr) {
|
||||||
|
resp.Error = NewJSRequiredApiLevelError()
|
||||||
|
s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if isJSONObjectOrArray(msg) {
|
if isJSONObjectOrArray(msg) {
|
||||||
if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil {
|
if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil {
|
||||||
|
|||||||
+653
@@ -0,0 +1,653 @@
|
|||||||
|
// Copyright 2025 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Tracks the total inflight batches, across all streams and accounts that enable batching.
|
||||||
|
globalInflightBatches atomic.Int32
|
||||||
|
)
|
||||||
|
|
||||||
|
type batching struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
group map[string]*batchGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
type batchGroup struct {
|
||||||
|
lseq uint64
|
||||||
|
store StreamStore
|
||||||
|
timer *time.Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock should be held.
|
||||||
|
func (batches *batching) newBatchGroup(mset *stream, batchId string) (*batchGroup, error) {
|
||||||
|
store, err := newBatchStore(mset, batchId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b := &batchGroup{store: store}
|
||||||
|
|
||||||
|
// Create a timer to clean up after timeout.
|
||||||
|
timeout := streamMaxBatchTimeout
|
||||||
|
if maxBatchTimeout := mset.srv.getOpts().JetStreamLimits.MaxBatchTimeout; maxBatchTimeout > 0 {
|
||||||
|
timeout = maxBatchTimeout
|
||||||
|
}
|
||||||
|
b.timer = time.AfterFunc(timeout, func() {
|
||||||
|
b.cleanup(batchId, batches)
|
||||||
|
mset.sendStreamBatchAbandonedAdvisory(batchId, BatchTimeout)
|
||||||
|
})
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBatchStoreDir(mset *stream, batchId string) (string, string) {
|
||||||
|
mset.mu.RLock()
|
||||||
|
jsa, name := mset.jsa, mset.cfg.Name
|
||||||
|
mset.mu.RUnlock()
|
||||||
|
|
||||||
|
jsa.mu.RLock()
|
||||||
|
sd := jsa.storeDir
|
||||||
|
jsa.mu.RUnlock()
|
||||||
|
|
||||||
|
bname := getHash(batchId)
|
||||||
|
return bname, filepath.Join(sd, streamsDir, name, batchesDir, bname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBatchStore(mset *stream, batchId string) (StreamStore, error) {
|
||||||
|
mset.mu.RLock()
|
||||||
|
replicas, storage := mset.cfg.Replicas, mset.cfg.Storage
|
||||||
|
mset.mu.RUnlock()
|
||||||
|
|
||||||
|
if replicas == 1 && storage == FileStorage {
|
||||||
|
bname, storeDir := getBatchStoreDir(mset, batchId)
|
||||||
|
fcfg := FileStoreConfig{AsyncFlush: true, BlockSize: defaultLargeBlockSize, StoreDir: storeDir}
|
||||||
|
s := mset.srv
|
||||||
|
prf := s.jsKeyGen(s.getOpts().JetStreamKey, mset.acc.Name)
|
||||||
|
if prf != nil {
|
||||||
|
// We are encrypted here, fill in correct cipher selection.
|
||||||
|
fcfg.Cipher = s.getOpts().JetStreamCipher
|
||||||
|
}
|
||||||
|
oldprf := s.jsKeyGen(s.getOpts().JetStreamOldKey, mset.acc.Name)
|
||||||
|
cfg := StreamConfig{Name: bname, Storage: FileStorage}
|
||||||
|
return newFileStoreWithCreated(fcfg, cfg, time.Time{}, prf, oldprf)
|
||||||
|
}
|
||||||
|
return newMemStore(&StreamConfig{Name: _EMPTY_, Storage: MemoryStorage})
|
||||||
|
}
|
||||||
|
|
||||||
|
// readyForCommit indicates the batch is ready to be committed.
|
||||||
|
// If the timer has already cleaned up the batch, we can't commit.
|
||||||
|
// Otherwise, we ensure the timer does not clean up the batch in the meantime.
|
||||||
|
// Lock should be held.
|
||||||
|
func (b *batchGroup) readyForCommit() bool {
|
||||||
|
if !b.timer.Stop() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b.store.FlushAllPending()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup deletes underlying resources associated with the batch and unregisters it from the stream's batches.
|
||||||
|
func (b *batchGroup) cleanup(batchId string, batches *batching) {
|
||||||
|
batches.mu.Lock()
|
||||||
|
defer batches.mu.Unlock()
|
||||||
|
b.cleanupLocked(batchId, batches)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock should be held.
|
||||||
|
func (b *batchGroup) cleanupLocked(batchId string, batches *batching) {
|
||||||
|
globalInflightBatches.Add(-1)
|
||||||
|
b.timer.Stop()
|
||||||
|
b.store.Delete(true)
|
||||||
|
delete(batches.group, batchId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock should be held.
|
||||||
|
func (b *batchGroup) stopLocked() {
|
||||||
|
globalInflightBatches.Add(-1)
|
||||||
|
b.timer.Stop()
|
||||||
|
b.store.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// batchStagedDiff stages all changes for consistency checks until commit.
|
||||||
|
type batchStagedDiff struct {
|
||||||
|
msgIds map[string]struct{}
|
||||||
|
counter map[string]*msgCounterRunningTotal
|
||||||
|
inflight map[string]*inflightSubjectRunningTotal
|
||||||
|
expectedPerSubject map[string]*batchExpectedPerSubject
|
||||||
|
}
|
||||||
|
|
||||||
|
type batchExpectedPerSubject struct {
|
||||||
|
sseq uint64 // Stream sequence.
|
||||||
|
clseq uint64 // Clustered proposal sequence.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (diff *batchStagedDiff) commit(mset *stream) {
|
||||||
|
if len(diff.msgIds) > 0 {
|
||||||
|
ts := time.Now().UnixNano()
|
||||||
|
mset.ddMu.Lock()
|
||||||
|
for msgId := range diff.msgIds {
|
||||||
|
// We stage with zero, and will update in processJetStreamMsg once we know the sequence.
|
||||||
|
mset.storeMsgIdLocked(&ddentry{msgId, 0, ts})
|
||||||
|
}
|
||||||
|
mset.ddMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store running totals for counters, we could have multiple counter increments proposed, but not applied yet.
|
||||||
|
if len(diff.counter) > 0 {
|
||||||
|
if mset.clusteredCounterTotal == nil {
|
||||||
|
mset.clusteredCounterTotal = make(map[string]*msgCounterRunningTotal, len(diff.counter))
|
||||||
|
}
|
||||||
|
for k, c := range diff.counter {
|
||||||
|
mset.clusteredCounterTotal[k] = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track inflight.
|
||||||
|
if len(diff.inflight) > 0 {
|
||||||
|
if mset.inflight == nil {
|
||||||
|
mset.inflight = make(map[string]*inflightSubjectRunningTotal, len(diff.inflight))
|
||||||
|
}
|
||||||
|
for subj, i := range diff.inflight {
|
||||||
|
if c, ok := mset.inflight[subj]; ok {
|
||||||
|
c.bytes += i.bytes
|
||||||
|
c.ops += i.ops
|
||||||
|
} else {
|
||||||
|
mset.inflight[subj] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track sequence and subject.
|
||||||
|
if len(diff.expectedPerSubject) > 0 {
|
||||||
|
if mset.expectedPerSubjectSequence == nil {
|
||||||
|
mset.expectedPerSubjectSequence = make(map[uint64]string, len(diff.expectedPerSubject))
|
||||||
|
}
|
||||||
|
if mset.expectedPerSubjectInProcess == nil {
|
||||||
|
mset.expectedPerSubjectInProcess = make(map[string]struct{}, len(diff.expectedPerSubject))
|
||||||
|
}
|
||||||
|
for subj, e := range diff.expectedPerSubject {
|
||||||
|
mset.expectedPerSubjectSequence[e.clseq] = subj
|
||||||
|
mset.expectedPerSubjectInProcess[subj] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type batchApply struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
id string // ID of the current batch.
|
||||||
|
count uint64 // Number of entries in the batch, for consistency checks.
|
||||||
|
entries []*CommittedEntry // Previous entries that are part of this batch.
|
||||||
|
entryStart int // The index into an entry indicating the first message of the batch.
|
||||||
|
maxApplied uint64 // Applied value before the entry containing the first message of the batch.
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearBatchStateLocked clears in-memory apply-batch-related state.
|
||||||
|
// batch.mu lock should be held.
|
||||||
|
func (batch *batchApply) clearBatchStateLocked() {
|
||||||
|
batch.id = _EMPTY_
|
||||||
|
batch.count = 0
|
||||||
|
batch.entries = nil
|
||||||
|
batch.entryStart = 0
|
||||||
|
batch.maxApplied = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// rejectBatchStateLocked rejects the batch and clears in-memory apply-batch-related state.
|
||||||
|
// Corrects mset.clfs to take the failed batch into account.
|
||||||
|
// batch.mu lock should be held.
|
||||||
|
func (batch *batchApply) rejectBatchStateLocked(mset *stream) {
|
||||||
|
mset.clMu.Lock()
|
||||||
|
mset.clfs += batch.count
|
||||||
|
mset.clMu.Unlock()
|
||||||
|
// We're rejecting the batch, so all entries need to be returned to the pool.
|
||||||
|
for _, bce := range batch.entries {
|
||||||
|
bce.ReturnToPool()
|
||||||
|
}
|
||||||
|
batch.clearBatchStateLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (batch *batchApply) rejectBatchState(mset *stream) {
|
||||||
|
batch.mu.Lock()
|
||||||
|
defer batch.mu.Unlock()
|
||||||
|
batch.rejectBatchStateLocked(mset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkMsgHeadersPreClusteredProposal checks the message for expected/consistency headers.
|
||||||
|
// mset.mu lock must NOT be held or used.
|
||||||
|
// mset.clMu lock must be held.
|
||||||
|
func checkMsgHeadersPreClusteredProposal(
|
||||||
|
diff *batchStagedDiff, mset *stream, subject string, hdr []byte, msg []byte, sourced bool, name string,
|
||||||
|
jsa *jsAccount, allowRollup, denyPurge, allowTTL, allowMsgCounter, allowMsgSchedules bool,
|
||||||
|
discard DiscardPolicy, discardNewPer bool, maxMsgSize int, maxMsgs int64, maxMsgsPer int64, maxBytes int64,
|
||||||
|
) ([]byte, []byte, uint64, *ApiError, error) {
|
||||||
|
var incr *big.Int
|
||||||
|
|
||||||
|
// Some header checks must be checked pre proposal.
|
||||||
|
if len(hdr) > 0 {
|
||||||
|
// Since we encode header len as u16 make sure we do not exceed.
|
||||||
|
// Again this works if it goes through but better to be pre-emptive.
|
||||||
|
if len(hdr) > math.MaxUint16 {
|
||||||
|
err := fmt.Errorf("JetStream header size exceeds limits for '%s > %s'", jsa.acc().Name, mset.cfg.Name)
|
||||||
|
return hdr, msg, 0, NewJSStreamHeaderExceedsMaximumError(), err
|
||||||
|
}
|
||||||
|
// Counter increments.
|
||||||
|
// Only supported on counter streams, and payload must be empty (if not coming from a source).
|
||||||
|
var ok bool
|
||||||
|
if incr, ok = getMessageIncr(hdr); !ok {
|
||||||
|
apiErr := NewJSMessageIncrInvalidError()
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
} else if incr != nil && !sourced {
|
||||||
|
// Only do checks if the message isn't sourced. Otherwise, we need to store verbatim.
|
||||||
|
if !allowMsgCounter {
|
||||||
|
apiErr := NewJSMessageIncrDisabledError()
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
} else if len(msg) > 0 {
|
||||||
|
apiErr := NewJSMessageIncrPayloadError()
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
} else {
|
||||||
|
// Check for incompatible headers.
|
||||||
|
var doErr bool
|
||||||
|
if getRollup(hdr) != _EMPTY_ ||
|
||||||
|
getExpectedStream(hdr) != _EMPTY_ ||
|
||||||
|
getExpectedLastMsgId(hdr) != _EMPTY_ ||
|
||||||
|
getExpectedLastSeqPerSubjectForSubject(hdr) != _EMPTY_ {
|
||||||
|
doErr = true
|
||||||
|
} else if _, ok = getExpectedLastSeq(hdr); ok {
|
||||||
|
doErr = true
|
||||||
|
} else if _, ok = getExpectedLastSeqPerSubject(hdr); ok {
|
||||||
|
doErr = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if doErr {
|
||||||
|
apiErr := NewJSMessageIncrInvalidError()
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Expected stream name can also be pre-checked.
|
||||||
|
if sname := getExpectedStream(hdr); sname != _EMPTY_ && sname != name {
|
||||||
|
return hdr, msg, 0, NewJSStreamNotMatchError(), errStreamMismatch
|
||||||
|
}
|
||||||
|
// TTL'd messages are rejected entirely if TTLs are not enabled on the stream, or if the TTL is invalid.
|
||||||
|
if ttl, err := getMessageTTL(hdr); !sourced && (ttl != 0 || err != nil) {
|
||||||
|
if !allowTTL {
|
||||||
|
return hdr, msg, 0, NewJSMessageTTLDisabledError(), errMsgTTLDisabled
|
||||||
|
} else if err != nil {
|
||||||
|
return hdr, msg, 0, NewJSMessageTTLInvalidError(), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for MsgIds here at the cluster level to avoid excessive CLFS accounting.
|
||||||
|
// Will help during restarts.
|
||||||
|
if msgId := getMsgId(hdr); msgId != _EMPTY_ {
|
||||||
|
// Dedupe if staged.
|
||||||
|
if _, ok = diff.msgIds[msgId]; ok {
|
||||||
|
return hdr, msg, 0, nil, errMsgIdDuplicate
|
||||||
|
}
|
||||||
|
mset.ddMu.Lock()
|
||||||
|
if dde := mset.checkMsgId(msgId); dde != nil {
|
||||||
|
seq := dde.seq
|
||||||
|
mset.ddMu.Unlock()
|
||||||
|
// Should not return an invalid sequence, in that case error.
|
||||||
|
if seq > 0 {
|
||||||
|
return hdr, msg, seq, nil, errMsgIdDuplicate
|
||||||
|
} else {
|
||||||
|
return hdr, msg, 0, NewJSStreamDuplicateMessageConflictError(), errMsgIdDuplicate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if diff.msgIds == nil {
|
||||||
|
diff.msgIds = map[string]struct{}{msgId: {}}
|
||||||
|
} else {
|
||||||
|
diff.msgIds[msgId] = struct{}{}
|
||||||
|
}
|
||||||
|
mset.ddMu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply increment for counter.
|
||||||
|
// But only if it's allowed for this stream. This can happen when we store verbatim for a sourced stream.
|
||||||
|
if incr == nil && allowMsgCounter {
|
||||||
|
apiErr := NewJSMessageIncrMissingError()
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
}
|
||||||
|
if incr != nil && allowMsgCounter {
|
||||||
|
var initial big.Int
|
||||||
|
var sources CounterSources
|
||||||
|
|
||||||
|
// If we've got a running total, update that, since we have inflight proposals updating the same counter.
|
||||||
|
var ok bool
|
||||||
|
var counter *msgCounterRunningTotal
|
||||||
|
if counter, ok = diff.counter[subject]; ok {
|
||||||
|
initial = *counter.total
|
||||||
|
sources = counter.sources
|
||||||
|
} else if counter, ok = mset.clusteredCounterTotal[subject]; ok {
|
||||||
|
initial = *counter.total
|
||||||
|
sources = counter.sources
|
||||||
|
// Make an explicit copy to separate the staged data from what's committed.
|
||||||
|
// Don't need to initialize all values, they'll be overwritten later.
|
||||||
|
counter = &msgCounterRunningTotal{ops: counter.ops}
|
||||||
|
} else {
|
||||||
|
// Load last message, and store as inflight running total.
|
||||||
|
var smv StoreMsg
|
||||||
|
sm, err := mset.store.LoadLastMsg(subject, &smv)
|
||||||
|
if err == nil && sm != nil {
|
||||||
|
var val CounterValue
|
||||||
|
// Return an error if the counter is broken somehow.
|
||||||
|
if json.Unmarshal(sm.msg, &val) != nil {
|
||||||
|
apiErr := NewJSMessageCounterBrokenError()
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
}
|
||||||
|
if ncs := sliceHeader(JSMessageCounterSources, sm.hdr); len(ncs) > 0 {
|
||||||
|
if err := json.Unmarshal(ncs, &sources); err != nil {
|
||||||
|
apiErr := NewJSMessageCounterBrokenError()
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initial.SetString(val.Value, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
srchdr := sliceHeader(JSStreamSource, hdr)
|
||||||
|
if len(srchdr) > 0 {
|
||||||
|
// This is a sourced message, so we can't apply Nats-Incr but
|
||||||
|
// instead should just update the source count header.
|
||||||
|
fields := strings.Split(string(srchdr), " ")
|
||||||
|
origStream := fields[0]
|
||||||
|
origSubj := subject
|
||||||
|
if len(fields) >= 5 {
|
||||||
|
origSubj = fields[4]
|
||||||
|
}
|
||||||
|
var val CounterValue
|
||||||
|
if json.Unmarshal(msg, &val) != nil {
|
||||||
|
apiErr := NewJSMessageCounterBrokenError()
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
}
|
||||||
|
var sourced big.Int
|
||||||
|
sourced.SetString(val.Value, 10)
|
||||||
|
if sources == nil {
|
||||||
|
sources = map[string]map[string]string{}
|
||||||
|
}
|
||||||
|
if _, ok = sources[origStream]; !ok {
|
||||||
|
sources[origStream] = map[string]string{}
|
||||||
|
}
|
||||||
|
prevVal := sources[origStream][origSubj]
|
||||||
|
sources[origStream][origSubj] = sourced.String()
|
||||||
|
// We will also replace the Nats-Incr header with the diff
|
||||||
|
// between our last value from this source and this one, so
|
||||||
|
// that the arithmetic is always correct.
|
||||||
|
var previous big.Int
|
||||||
|
previous.SetString(prevVal, 10)
|
||||||
|
incr.Sub(&sourced, &previous)
|
||||||
|
hdr = setHeader(JSMessageIncr, incr.String(), hdr)
|
||||||
|
}
|
||||||
|
// Now make the change.
|
||||||
|
initial.Add(&initial, incr)
|
||||||
|
// Generate the new payload.
|
||||||
|
var _msg [128]byte
|
||||||
|
msg = fmt.Appendf(_msg[:0], "{%q:%q}", "val", initial.String())
|
||||||
|
// Write the updated source count headers.
|
||||||
|
if len(sources) > 0 {
|
||||||
|
nhdr, err := json.Marshal(sources)
|
||||||
|
if err != nil {
|
||||||
|
return hdr, msg, 0, NewJSMessageCounterBrokenError(), err
|
||||||
|
}
|
||||||
|
hdr = setHeader(JSMessageCounterSources, string(nhdr), hdr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if we are over the max msg size.
|
||||||
|
maxSize := int64(mset.srv.getOpts().MaxPayload)
|
||||||
|
if maxMsgSize >= 0 && int64(maxMsgSize) < maxSize {
|
||||||
|
maxSize = int64(maxMsgSize)
|
||||||
|
}
|
||||||
|
hdrLen, msgLen := int64(len(hdr)), int64(len(msg))
|
||||||
|
// Subtract to prevent against overflows.
|
||||||
|
if hdrLen > maxSize || msgLen > maxSize-hdrLen {
|
||||||
|
return hdr, msg, 0, NewJSStreamMessageExceedsMaximumError(), ErrMaxPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the in-memory counters up-to-date.
|
||||||
|
if counter == nil {
|
||||||
|
counter = &msgCounterRunningTotal{}
|
||||||
|
}
|
||||||
|
counter.total = &initial
|
||||||
|
counter.sources = sources
|
||||||
|
counter.ops++
|
||||||
|
if diff.counter == nil {
|
||||||
|
diff.counter = map[string]*msgCounterRunningTotal{subject: counter}
|
||||||
|
} else {
|
||||||
|
diff.counter[subject] = counter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hdr) > 0 {
|
||||||
|
// Expected last sequence.
|
||||||
|
if seq, exists := getExpectedLastSeq(hdr); exists && seq != mset.clseq-mset.clfs {
|
||||||
|
mlseq := mset.clseq - mset.clfs
|
||||||
|
err := fmt.Errorf("last sequence mismatch: %d vs %d", seq, mlseq)
|
||||||
|
return hdr, msg, 0, NewJSStreamWrongLastSequenceError(mlseq), err
|
||||||
|
} else if exists && len(diff.inflight) > 0 {
|
||||||
|
// Only the first message in a batch can contain an expected last sequence.
|
||||||
|
err := fmt.Errorf("last sequence mismatch")
|
||||||
|
return hdr, msg, 0, NewJSStreamWrongLastSequenceConstantError(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expected last sequence per subject.
|
||||||
|
if seq, exists := getExpectedLastSeqPerSubject(hdr); exists {
|
||||||
|
// Allow override of the subject used for the check.
|
||||||
|
seqSubj := subject
|
||||||
|
if optSubj := getExpectedLastSeqPerSubjectForSubject(hdr); optSubj != _EMPTY_ {
|
||||||
|
seqSubj = optSubj
|
||||||
|
}
|
||||||
|
|
||||||
|
// The subject is already written to in this batch, we can't allow
|
||||||
|
// expected checks since they would be incorrect.
|
||||||
|
if _, ok := diff.inflight[seqSubj]; ok {
|
||||||
|
err := errors.New("last sequence by subject mismatch")
|
||||||
|
return hdr, msg, 0, NewJSStreamWrongLastSequenceConstantError(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the subject is already in process, block as otherwise we could have
|
||||||
|
// multiple messages inflight with the same subject.
|
||||||
|
if _, found := mset.expectedPerSubjectInProcess[seqSubj]; found {
|
||||||
|
err := errors.New("last sequence by subject mismatch")
|
||||||
|
return hdr, msg, 0, NewJSStreamWrongLastSequenceConstantError(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the subject is already in process but without expected headers, block as we would have
|
||||||
|
// multiple messages inflight with the same subject.
|
||||||
|
if _, ok := mset.inflight[seqSubj]; ok {
|
||||||
|
err := errors.New("last sequence by subject mismatch")
|
||||||
|
return hdr, msg, 0, NewJSStreamWrongLastSequenceConstantError(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've already done an expected-check on this subject, use the cached result.
|
||||||
|
if e, ok := diff.expectedPerSubject[seqSubj]; ok {
|
||||||
|
if e.sseq != seq {
|
||||||
|
err := fmt.Errorf("last sequence by subject mismatch: %d vs %d", seq, e.sseq)
|
||||||
|
return hdr, msg, 0, NewJSStreamWrongLastSequenceError(e.sseq), err
|
||||||
|
}
|
||||||
|
e.clseq = mset.clseq
|
||||||
|
} else {
|
||||||
|
var smv StoreMsg
|
||||||
|
var fseq uint64
|
||||||
|
sm, err := mset.store.LoadLastMsg(seqSubj, &smv)
|
||||||
|
if sm != nil {
|
||||||
|
fseq = sm.seq
|
||||||
|
}
|
||||||
|
if err == ErrStoreMsgNotFound && seq == 0 {
|
||||||
|
fseq, err = 0, nil
|
||||||
|
}
|
||||||
|
if err != nil || fseq != seq {
|
||||||
|
err = fmt.Errorf("last sequence by subject mismatch: %d vs %d", seq, fseq)
|
||||||
|
return hdr, msg, 0, NewJSStreamWrongLastSequenceError(fseq), err
|
||||||
|
}
|
||||||
|
|
||||||
|
e = &batchExpectedPerSubject{sseq: fseq, clseq: mset.clseq}
|
||||||
|
if diff.expectedPerSubject == nil {
|
||||||
|
diff.expectedPerSubject = map[string]*batchExpectedPerSubject{seqSubj: e}
|
||||||
|
} else {
|
||||||
|
diff.expectedPerSubject[seqSubj] = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if getExpectedLastSeqPerSubjectForSubject(hdr) != _EMPTY_ {
|
||||||
|
apiErr := NewJSStreamExpectedLastSeqPerSubjectInvalidError()
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message scheduling.
|
||||||
|
if schedule, ok := getMessageSchedule(hdr); !ok {
|
||||||
|
apiErr := NewJSMessageSchedulesPatternInvalidError()
|
||||||
|
if !allowMsgSchedules {
|
||||||
|
apiErr = NewJSMessageSchedulesDisabledError()
|
||||||
|
}
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
} else if !schedule.IsZero() {
|
||||||
|
if !allowMsgSchedules {
|
||||||
|
apiErr := NewJSMessageSchedulesDisabledError()
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
} else if scheduleTtl, ok := getMessageScheduleTTL(hdr); !ok {
|
||||||
|
apiErr := NewJSMessageSchedulesTTLInvalidError()
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
} else if scheduleTtl != _EMPTY_ && !allowTTL {
|
||||||
|
return hdr, msg, 0, NewJSMessageTTLDisabledError(), errMsgTTLDisabled
|
||||||
|
} else if scheduleTarget := getMessageScheduleTarget(hdr); scheduleTarget == _EMPTY_ ||
|
||||||
|
!IsValidPublishSubject(scheduleTarget) || SubjectsCollide(scheduleTarget, subject) {
|
||||||
|
apiErr := NewJSMessageSchedulesTargetInvalidError()
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
} else {
|
||||||
|
mset.cfgMu.RLock()
|
||||||
|
match := slices.ContainsFunc(mset.cfg.Subjects, func(subj string) bool {
|
||||||
|
return SubjectsCollide(subj, scheduleTarget)
|
||||||
|
})
|
||||||
|
mset.cfgMu.RUnlock()
|
||||||
|
if !match {
|
||||||
|
apiErr := NewJSMessageSchedulesTargetInvalidError()
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a rollup sub header if it doesn't already exist.
|
||||||
|
// Otherwise, it must exist already as a rollup on the subject.
|
||||||
|
if rollup := getRollup(hdr); rollup == _EMPTY_ {
|
||||||
|
hdr = genHeader(hdr, JSMsgRollup, JSMsgRollupSubject)
|
||||||
|
} else if rollup != JSMsgRollupSubject {
|
||||||
|
apiErr := NewJSMessageSchedulesRollupInvalidError()
|
||||||
|
return hdr, msg, 0, apiErr, apiErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for any rollups.
|
||||||
|
if rollup := getRollup(hdr); rollup != _EMPTY_ {
|
||||||
|
if !allowRollup || denyPurge {
|
||||||
|
err := errors.New("rollup not permitted")
|
||||||
|
return hdr, msg, 0, NewJSStreamRollupFailedError(err), err
|
||||||
|
}
|
||||||
|
switch rollup {
|
||||||
|
case JSMsgRollupSubject:
|
||||||
|
// Rolling up the subject is only allowed if the first occurrence of this subject in the batch.
|
||||||
|
if _, ok := diff.inflight[subject]; ok {
|
||||||
|
err := errors.New("batch rollup sub invalid")
|
||||||
|
return hdr, msg, 0, NewJSStreamRollupFailedError(err), err
|
||||||
|
}
|
||||||
|
case JSMsgRollupAll:
|
||||||
|
// Rolling up the whole stream is only allowed if this is the first message of the batch.
|
||||||
|
if len(diff.inflight) > 0 {
|
||||||
|
err := errors.New("batch rollup all invalid")
|
||||||
|
return hdr, msg, 0, NewJSStreamRollupFailedError(err), err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("rollup value invalid: %q", rollup)
|
||||||
|
return hdr, msg, 0, NewJSStreamRollupFailedError(err), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track inflight.
|
||||||
|
// Store the subject to ensure other messages in this batch using
|
||||||
|
// an expected check or rollup on the same subject fail.
|
||||||
|
if diff.inflight == nil {
|
||||||
|
diff.inflight = make(map[string]*inflightSubjectRunningTotal, 1)
|
||||||
|
}
|
||||||
|
var sz uint64
|
||||||
|
if mset.store.Type() == FileStorage {
|
||||||
|
sz = fileStoreMsgSizeRaw(len(subject), len(hdr), len(msg))
|
||||||
|
} else {
|
||||||
|
sz = memStoreMsgSizeRaw(len(subject), len(hdr), len(msg))
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
i *inflightSubjectRunningTotal
|
||||||
|
ok bool
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if i, ok = diff.inflight[subject]; ok {
|
||||||
|
i.bytes += sz
|
||||||
|
i.ops++
|
||||||
|
} else {
|
||||||
|
i = &inflightSubjectRunningTotal{bytes: sz, ops: 1}
|
||||||
|
diff.inflight[subject] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have discard new with max msgs or bytes.
|
||||||
|
// We need to deny here otherwise we'd need to bump CLFS, and it could succeed on some
|
||||||
|
// peers and not others depending on consumer ack state (if interest policy).
|
||||||
|
// So we deny here, if we allow that means we know it would succeed on every peer.
|
||||||
|
if discard == DiscardNew && (maxMsgs > 0 || maxBytes > 0) {
|
||||||
|
// Error if over DiscardNew per subject threshold.
|
||||||
|
if discardNewPer {
|
||||||
|
totalMsgsForSubject := i.ops
|
||||||
|
if i, ok = mset.inflight[subject]; ok {
|
||||||
|
totalMsgsForSubject += i.ops
|
||||||
|
}
|
||||||
|
if maxMsgsPer > 0 && totalMsgsForSubject > uint64(maxMsgsPer) {
|
||||||
|
err = ErrMaxMsgsPerSubject
|
||||||
|
return hdr, msg, 0, NewJSStreamStoreFailedError(err, Unless(err)), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track usual max msgs/bytes thresholds for DiscardNew.
|
||||||
|
var state StreamState
|
||||||
|
mset.store.FastState(&state)
|
||||||
|
|
||||||
|
totalMsgs := state.Msgs
|
||||||
|
totalBytes := state.Bytes
|
||||||
|
for _, i = range mset.inflight {
|
||||||
|
totalMsgs += i.ops
|
||||||
|
totalBytes += i.bytes
|
||||||
|
}
|
||||||
|
for _, i = range diff.inflight {
|
||||||
|
totalMsgs += i.ops
|
||||||
|
totalBytes += i.bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxMsgs > 0 && totalMsgs > uint64(maxMsgs) {
|
||||||
|
err = ErrMaxMsgs
|
||||||
|
} else if maxBytes > 0 && totalBytes > uint64(maxBytes) {
|
||||||
|
err = ErrMaxBytes
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return hdr, msg, 0, NewJSStreamStoreFailedError(err, Unless(err)), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hdr, msg, 0, nil, nil
|
||||||
|
}
|
||||||
+706
-404
File diff suppressed because it is too large
Load Diff
+592
-174
@@ -8,6 +8,24 @@ const (
|
|||||||
// JSAccountResourcesExceededErr resource limits exceeded for account
|
// JSAccountResourcesExceededErr resource limits exceeded for account
|
||||||
JSAccountResourcesExceededErr ErrorIdentifier = 10002
|
JSAccountResourcesExceededErr ErrorIdentifier = 10002
|
||||||
|
|
||||||
|
// JSAtomicPublishDisabledErr atomic publish is disabled
|
||||||
|
JSAtomicPublishDisabledErr ErrorIdentifier = 10174
|
||||||
|
|
||||||
|
// JSAtomicPublishIncompleteBatchErr atomic publish batch is incomplete
|
||||||
|
JSAtomicPublishIncompleteBatchErr ErrorIdentifier = 10176
|
||||||
|
|
||||||
|
// JSAtomicPublishInvalidBatchIDErr atomic publish batch ID is invalid
|
||||||
|
JSAtomicPublishInvalidBatchIDErr ErrorIdentifier = 10179
|
||||||
|
|
||||||
|
// JSAtomicPublishMissingSeqErr atomic publish sequence is missing
|
||||||
|
JSAtomicPublishMissingSeqErr ErrorIdentifier = 10175
|
||||||
|
|
||||||
|
// JSAtomicPublishTooLargeBatchErrF atomic publish batch is too large: {size}
|
||||||
|
JSAtomicPublishTooLargeBatchErrF ErrorIdentifier = 10199
|
||||||
|
|
||||||
|
// JSAtomicPublishUnsupportedHeaderBatchErr atomic publish unsupported header used: {header}
|
||||||
|
JSAtomicPublishUnsupportedHeaderBatchErr ErrorIdentifier = 10177
|
||||||
|
|
||||||
// JSBadRequestErr bad request
|
// JSBadRequestErr bad request
|
||||||
JSBadRequestErr ErrorIdentifier = 10003
|
JSBadRequestErr ErrorIdentifier = 10003
|
||||||
|
|
||||||
@@ -44,9 +62,18 @@ const (
|
|||||||
// JSClusterUnSupportFeatureErr not currently supported in clustered mode
|
// JSClusterUnSupportFeatureErr not currently supported in clustered mode
|
||||||
JSClusterUnSupportFeatureErr ErrorIdentifier = 10036
|
JSClusterUnSupportFeatureErr ErrorIdentifier = 10036
|
||||||
|
|
||||||
|
// JSConsumerAckPolicyInvalidErr consumer ack policy invalid
|
||||||
|
JSConsumerAckPolicyInvalidErr ErrorIdentifier = 10181
|
||||||
|
|
||||||
|
// JSConsumerAckWaitNegativeErr consumer ack wait needs to be positive
|
||||||
|
JSConsumerAckWaitNegativeErr ErrorIdentifier = 10183
|
||||||
|
|
||||||
// JSConsumerAlreadyExists action CREATE is used for a existing consumer with a different config (consumer already exists)
|
// JSConsumerAlreadyExists action CREATE is used for a existing consumer with a different config (consumer already exists)
|
||||||
JSConsumerAlreadyExists ErrorIdentifier = 10148
|
JSConsumerAlreadyExists ErrorIdentifier = 10148
|
||||||
|
|
||||||
|
// JSConsumerBackOffNegativeErr consumer backoff needs to be positive
|
||||||
|
JSConsumerBackOffNegativeErr ErrorIdentifier = 10184
|
||||||
|
|
||||||
// JSConsumerBadDurableNameErr durable name can not contain '.', '*', '>'
|
// JSConsumerBadDurableNameErr durable name can not contain '.', '*', '>'
|
||||||
JSConsumerBadDurableNameErr ErrorIdentifier = 10103
|
JSConsumerBadDurableNameErr ErrorIdentifier = 10103
|
||||||
|
|
||||||
@@ -149,8 +176,8 @@ const (
|
|||||||
// JSConsumerMaxRequestBatchNegativeErr consumer max request batch needs to be > 0
|
// JSConsumerMaxRequestBatchNegativeErr consumer max request batch needs to be > 0
|
||||||
JSConsumerMaxRequestBatchNegativeErr ErrorIdentifier = 10114
|
JSConsumerMaxRequestBatchNegativeErr ErrorIdentifier = 10114
|
||||||
|
|
||||||
// JSConsumerMaxRequestExpiresToSmall consumer max request expires needs to be >= 1ms
|
// JSConsumerMaxRequestExpiresTooSmall consumer max request expires needs to be >= 1ms
|
||||||
JSConsumerMaxRequestExpiresToSmall ErrorIdentifier = 10115
|
JSConsumerMaxRequestExpiresTooSmall ErrorIdentifier = 10115
|
||||||
|
|
||||||
// JSConsumerMaxWaitingNegativeErr consumer max waiting needs to be positive
|
// JSConsumerMaxWaitingNegativeErr consumer max waiting needs to be positive
|
||||||
JSConsumerMaxWaitingNegativeErr ErrorIdentifier = 10087
|
JSConsumerMaxWaitingNegativeErr ErrorIdentifier = 10087
|
||||||
@@ -185,6 +212,12 @@ const (
|
|||||||
// JSConsumerOverlappingSubjectFilters consumer subject filters cannot overlap
|
// JSConsumerOverlappingSubjectFilters consumer subject filters cannot overlap
|
||||||
JSConsumerOverlappingSubjectFilters ErrorIdentifier = 10138
|
JSConsumerOverlappingSubjectFilters ErrorIdentifier = 10138
|
||||||
|
|
||||||
|
// JSConsumerPinnedTTLWithoutPriorityPolicyNone PinnedTTL cannot be set when PriorityPolicy is none
|
||||||
|
JSConsumerPinnedTTLWithoutPriorityPolicyNone ErrorIdentifier = 10197
|
||||||
|
|
||||||
|
// JSConsumerPriorityGroupWithPolicyNone consumer can not have priority groups when policy is none
|
||||||
|
JSConsumerPriorityGroupWithPolicyNone ErrorIdentifier = 10196
|
||||||
|
|
||||||
// JSConsumerPriorityPolicyWithoutGroup Setting PriorityPolicy requires at least one PriorityGroup to be set
|
// JSConsumerPriorityPolicyWithoutGroup Setting PriorityPolicy requires at least one PriorityGroup to be set
|
||||||
JSConsumerPriorityPolicyWithoutGroup ErrorIdentifier = 10159
|
JSConsumerPriorityPolicyWithoutGroup ErrorIdentifier = 10159
|
||||||
|
|
||||||
@@ -206,6 +239,9 @@ const (
|
|||||||
// JSConsumerReplacementWithDifferentNameErr consumer replacement durable config not the same
|
// JSConsumerReplacementWithDifferentNameErr consumer replacement durable config not the same
|
||||||
JSConsumerReplacementWithDifferentNameErr ErrorIdentifier = 10106
|
JSConsumerReplacementWithDifferentNameErr ErrorIdentifier = 10106
|
||||||
|
|
||||||
|
// JSConsumerReplayPolicyInvalidErr consumer replay policy invalid
|
||||||
|
JSConsumerReplayPolicyInvalidErr ErrorIdentifier = 10182
|
||||||
|
|
||||||
// JSConsumerReplicasExceedsStream consumer config replica count exceeds parent stream
|
// JSConsumerReplicasExceedsStream consumer config replica count exceeds parent stream
|
||||||
JSConsumerReplicasExceedsStream ErrorIdentifier = 10126
|
JSConsumerReplicasExceedsStream ErrorIdentifier = 10126
|
||||||
|
|
||||||
@@ -248,6 +284,36 @@ const (
|
|||||||
// JSMemoryResourcesExceededErr insufficient memory resources available
|
// JSMemoryResourcesExceededErr insufficient memory resources available
|
||||||
JSMemoryResourcesExceededErr ErrorIdentifier = 10028
|
JSMemoryResourcesExceededErr ErrorIdentifier = 10028
|
||||||
|
|
||||||
|
// JSMessageCounterBrokenErr message counter is broken
|
||||||
|
JSMessageCounterBrokenErr ErrorIdentifier = 10172
|
||||||
|
|
||||||
|
// JSMessageIncrDisabledErr message counters is disabled
|
||||||
|
JSMessageIncrDisabledErr ErrorIdentifier = 10168
|
||||||
|
|
||||||
|
// JSMessageIncrInvalidErr message counter increment is invalid
|
||||||
|
JSMessageIncrInvalidErr ErrorIdentifier = 10171
|
||||||
|
|
||||||
|
// JSMessageIncrMissingErr message counter increment is missing
|
||||||
|
JSMessageIncrMissingErr ErrorIdentifier = 10169
|
||||||
|
|
||||||
|
// JSMessageIncrPayloadErr message counter has payload
|
||||||
|
JSMessageIncrPayloadErr ErrorIdentifier = 10170
|
||||||
|
|
||||||
|
// JSMessageSchedulesDisabledErr message schedules is disabled
|
||||||
|
JSMessageSchedulesDisabledErr ErrorIdentifier = 10188
|
||||||
|
|
||||||
|
// JSMessageSchedulesPatternInvalidErr message schedules pattern is invalid
|
||||||
|
JSMessageSchedulesPatternInvalidErr ErrorIdentifier = 10189
|
||||||
|
|
||||||
|
// JSMessageSchedulesRollupInvalidErr message schedules invalid rollup
|
||||||
|
JSMessageSchedulesRollupInvalidErr ErrorIdentifier = 10192
|
||||||
|
|
||||||
|
// JSMessageSchedulesTTLInvalidErr message schedules invalid per-message TTL
|
||||||
|
JSMessageSchedulesTTLInvalidErr ErrorIdentifier = 10191
|
||||||
|
|
||||||
|
// JSMessageSchedulesTargetInvalidErr message schedules target is invalid
|
||||||
|
JSMessageSchedulesTargetInvalidErr ErrorIdentifier = 10190
|
||||||
|
|
||||||
// JSMessageTTLDisabledErr per-message TTL is disabled
|
// JSMessageTTLDisabledErr per-message TTL is disabled
|
||||||
JSMessageTTLDisabledErr ErrorIdentifier = 10166
|
JSMessageTTLDisabledErr ErrorIdentifier = 10166
|
||||||
|
|
||||||
@@ -275,9 +341,18 @@ const (
|
|||||||
// JSMirrorOverlappingSubjectFilters mirror subject filters can not overlap
|
// JSMirrorOverlappingSubjectFilters mirror subject filters can not overlap
|
||||||
JSMirrorOverlappingSubjectFilters ErrorIdentifier = 10152
|
JSMirrorOverlappingSubjectFilters ErrorIdentifier = 10152
|
||||||
|
|
||||||
|
// JSMirrorWithAtomicPublishErr stream mirrors can not also use atomic publishing
|
||||||
|
JSMirrorWithAtomicPublishErr ErrorIdentifier = 10198
|
||||||
|
|
||||||
|
// JSMirrorWithCountersErr stream mirrors can not also calculate counters
|
||||||
|
JSMirrorWithCountersErr ErrorIdentifier = 10173
|
||||||
|
|
||||||
// JSMirrorWithFirstSeqErr stream mirrors can not have first sequence configured
|
// JSMirrorWithFirstSeqErr stream mirrors can not have first sequence configured
|
||||||
JSMirrorWithFirstSeqErr ErrorIdentifier = 10143
|
JSMirrorWithFirstSeqErr ErrorIdentifier = 10143
|
||||||
|
|
||||||
|
// JSMirrorWithMsgSchedulesErr stream mirrors can not also schedule messages
|
||||||
|
JSMirrorWithMsgSchedulesErr ErrorIdentifier = 10186
|
||||||
|
|
||||||
// JSMirrorWithSourcesErr stream mirrors can not also contain other sources
|
// JSMirrorWithSourcesErr stream mirrors can not also contain other sources
|
||||||
JSMirrorWithSourcesErr ErrorIdentifier = 10031
|
JSMirrorWithSourcesErr ErrorIdentifier = 10031
|
||||||
|
|
||||||
@@ -320,6 +395,9 @@ const (
|
|||||||
// JSReplicasCountCannotBeNegative replicas count cannot be negative
|
// JSReplicasCountCannotBeNegative replicas count cannot be negative
|
||||||
JSReplicasCountCannotBeNegative ErrorIdentifier = 10133
|
JSReplicasCountCannotBeNegative ErrorIdentifier = 10133
|
||||||
|
|
||||||
|
// JSRequiredApiLevelErr JetStream minimum api level required
|
||||||
|
JSRequiredApiLevelErr ErrorIdentifier = 10185
|
||||||
|
|
||||||
// JSRestoreSubscribeFailedErrF JetStream unable to subscribe to restore snapshot {subject}: {err}
|
// JSRestoreSubscribeFailedErrF JetStream unable to subscribe to restore snapshot {subject}: {err}
|
||||||
JSRestoreSubscribeFailedErrF ErrorIdentifier = 10042
|
JSRestoreSubscribeFailedErrF ErrorIdentifier = 10042
|
||||||
|
|
||||||
@@ -353,6 +431,9 @@ const (
|
|||||||
// JSSourceOverlappingSubjectFilters source filters can not overlap
|
// JSSourceOverlappingSubjectFilters source filters can not overlap
|
||||||
JSSourceOverlappingSubjectFilters ErrorIdentifier = 10147
|
JSSourceOverlappingSubjectFilters ErrorIdentifier = 10147
|
||||||
|
|
||||||
|
// JSSourceWithMsgSchedulesErr stream source can not also schedule messages
|
||||||
|
JSSourceWithMsgSchedulesErr ErrorIdentifier = 10187
|
||||||
|
|
||||||
// JSStorageResourcesExceededErr insufficient storage resources available
|
// JSStorageResourcesExceededErr insufficient storage resources available
|
||||||
JSStorageResourcesExceededErr ErrorIdentifier = 10047
|
JSStorageResourcesExceededErr ErrorIdentifier = 10047
|
||||||
|
|
||||||
@@ -368,6 +449,9 @@ const (
|
|||||||
// JSStreamDuplicateMessageConflict duplicate message id is in process
|
// JSStreamDuplicateMessageConflict duplicate message id is in process
|
||||||
JSStreamDuplicateMessageConflict ErrorIdentifier = 10158
|
JSStreamDuplicateMessageConflict ErrorIdentifier = 10158
|
||||||
|
|
||||||
|
// JSStreamExpectedLastSeqPerSubjectInvalid missing sequence for expected last sequence per subject
|
||||||
|
JSStreamExpectedLastSeqPerSubjectInvalid ErrorIdentifier = 10193
|
||||||
|
|
||||||
// JSStreamExpectedLastSeqPerSubjectNotReady expected last sequence per subject temporarily unavailable
|
// JSStreamExpectedLastSeqPerSubjectNotReady expected last sequence per subject temporarily unavailable
|
||||||
JSStreamExpectedLastSeqPerSubjectNotReady ErrorIdentifier = 10163
|
JSStreamExpectedLastSeqPerSubjectNotReady ErrorIdentifier = 10163
|
||||||
|
|
||||||
@@ -407,6 +491,9 @@ const (
|
|||||||
// JSStreamMessageExceedsMaximumErr message size exceeds maximum allowed
|
// JSStreamMessageExceedsMaximumErr message size exceeds maximum allowed
|
||||||
JSStreamMessageExceedsMaximumErr ErrorIdentifier = 10054
|
JSStreamMessageExceedsMaximumErr ErrorIdentifier = 10054
|
||||||
|
|
||||||
|
// JSStreamMinLastSeqErr min last sequence
|
||||||
|
JSStreamMinLastSeqErr ErrorIdentifier = 10180
|
||||||
|
|
||||||
// JSStreamMirrorNotUpdatableErr stream mirror configuration can not be updated
|
// JSStreamMirrorNotUpdatableErr stream mirror configuration can not be updated
|
||||||
JSStreamMirrorNotUpdatableErr ErrorIdentifier = 10055
|
JSStreamMirrorNotUpdatableErr ErrorIdentifier = 10055
|
||||||
|
|
||||||
@@ -515,175 +602,204 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
ApiErrors = map[ErrorIdentifier]*ApiError{
|
ApiErrors = map[ErrorIdentifier]*ApiError{
|
||||||
JSAccountResourcesExceededErr: {Code: 400, ErrCode: 10002, Description: "resource limits exceeded for account"},
|
JSAccountResourcesExceededErr: {Code: 400, ErrCode: 10002, Description: "resource limits exceeded for account"},
|
||||||
JSBadRequestErr: {Code: 400, ErrCode: 10003, Description: "bad request"},
|
JSAtomicPublishDisabledErr: {Code: 400, ErrCode: 10174, Description: "atomic publish is disabled"},
|
||||||
JSClusterIncompleteErr: {Code: 503, ErrCode: 10004, Description: "incomplete results"},
|
JSAtomicPublishIncompleteBatchErr: {Code: 400, ErrCode: 10176, Description: "atomic publish batch is incomplete"},
|
||||||
JSClusterNoPeersErrF: {Code: 400, ErrCode: 10005, Description: "{err}"},
|
JSAtomicPublishInvalidBatchIDErr: {Code: 400, ErrCode: 10179, Description: "atomic publish batch ID is invalid"},
|
||||||
JSClusterNotActiveErr: {Code: 500, ErrCode: 10006, Description: "JetStream not in clustered mode"},
|
JSAtomicPublishMissingSeqErr: {Code: 400, ErrCode: 10175, Description: "atomic publish sequence is missing"},
|
||||||
JSClusterNotAssignedErr: {Code: 500, ErrCode: 10007, Description: "JetStream cluster not assigned to this server"},
|
JSAtomicPublishTooLargeBatchErrF: {Code: 400, ErrCode: 10199, Description: "atomic publish batch is too large: {size}"},
|
||||||
JSClusterNotAvailErr: {Code: 503, ErrCode: 10008, Description: "JetStream system temporarily unavailable"},
|
JSAtomicPublishUnsupportedHeaderBatchErr: {Code: 400, ErrCode: 10177, Description: "atomic publish unsupported header used: {header}"},
|
||||||
JSClusterNotLeaderErr: {Code: 500, ErrCode: 10009, Description: "JetStream cluster can not handle request"},
|
JSBadRequestErr: {Code: 400, ErrCode: 10003, Description: "bad request"},
|
||||||
JSClusterPeerNotMemberErr: {Code: 400, ErrCode: 10040, Description: "peer not a member"},
|
JSClusterIncompleteErr: {Code: 503, ErrCode: 10004, Description: "incomplete results"},
|
||||||
JSClusterRequiredErr: {Code: 503, ErrCode: 10010, Description: "JetStream clustering support required"},
|
JSClusterNoPeersErrF: {Code: 400, ErrCode: 10005, Description: "{err}"},
|
||||||
JSClusterServerNotMemberErr: {Code: 400, ErrCode: 10044, Description: "server is not a member of the cluster"},
|
JSClusterNotActiveErr: {Code: 500, ErrCode: 10006, Description: "JetStream not in clustered mode"},
|
||||||
JSClusterTagsErr: {Code: 400, ErrCode: 10011, Description: "tags placement not supported for operation"},
|
JSClusterNotAssignedErr: {Code: 500, ErrCode: 10007, Description: "JetStream cluster not assigned to this server"},
|
||||||
JSClusterUnSupportFeatureErr: {Code: 503, ErrCode: 10036, Description: "not currently supported in clustered mode"},
|
JSClusterNotAvailErr: {Code: 503, ErrCode: 10008, Description: "JetStream system temporarily unavailable"},
|
||||||
JSConsumerAlreadyExists: {Code: 400, ErrCode: 10148, Description: "consumer already exists"},
|
JSClusterNotLeaderErr: {Code: 500, ErrCode: 10009, Description: "JetStream cluster can not handle request"},
|
||||||
JSConsumerBadDurableNameErr: {Code: 400, ErrCode: 10103, Description: "durable name can not contain '.', '*', '>'"},
|
JSClusterPeerNotMemberErr: {Code: 400, ErrCode: 10040, Description: "peer not a member"},
|
||||||
JSConsumerConfigRequiredErr: {Code: 400, ErrCode: 10078, Description: "consumer config required"},
|
JSClusterRequiredErr: {Code: 503, ErrCode: 10010, Description: "JetStream clustering support required"},
|
||||||
JSConsumerCreateDurableAndNameMismatch: {Code: 400, ErrCode: 10132, Description: "Consumer Durable and Name have to be equal if both are provided"},
|
JSClusterServerNotMemberErr: {Code: 400, ErrCode: 10044, Description: "server is not a member of the cluster"},
|
||||||
JSConsumerCreateErrF: {Code: 500, ErrCode: 10012, Description: "{err}"},
|
JSClusterTagsErr: {Code: 400, ErrCode: 10011, Description: "tags placement not supported for operation"},
|
||||||
JSConsumerCreateFilterSubjectMismatchErr: {Code: 400, ErrCode: 10131, Description: "Consumer create request did not match filtered subject from create subject"},
|
JSClusterUnSupportFeatureErr: {Code: 503, ErrCode: 10036, Description: "not currently supported in clustered mode"},
|
||||||
JSConsumerDeliverCycleErr: {Code: 400, ErrCode: 10081, Description: "consumer deliver subject forms a cycle"},
|
JSConsumerAckPolicyInvalidErr: {Code: 400, ErrCode: 10181, Description: "consumer ack policy invalid"},
|
||||||
JSConsumerDeliverToWildcardsErr: {Code: 400, ErrCode: 10079, Description: "consumer deliver subject has wildcards"},
|
JSConsumerAckWaitNegativeErr: {Code: 400, ErrCode: 10183, Description: "consumer ack wait needs to be positive"},
|
||||||
JSConsumerDescriptionTooLongErrF: {Code: 400, ErrCode: 10107, Description: "consumer description is too long, maximum allowed is {max}"},
|
JSConsumerAlreadyExists: {Code: 400, ErrCode: 10148, Description: "consumer already exists"},
|
||||||
JSConsumerDirectRequiresEphemeralErr: {Code: 400, ErrCode: 10091, Description: "consumer direct requires an ephemeral consumer"},
|
JSConsumerBackOffNegativeErr: {Code: 400, ErrCode: 10184, Description: "consumer backoff needs to be positive"},
|
||||||
JSConsumerDirectRequiresPushErr: {Code: 400, ErrCode: 10090, Description: "consumer direct requires a push based consumer"},
|
JSConsumerBadDurableNameErr: {Code: 400, ErrCode: 10103, Description: "durable name can not contain '.', '*', '>'"},
|
||||||
JSConsumerDoesNotExist: {Code: 400, ErrCode: 10149, Description: "consumer does not exist"},
|
JSConsumerConfigRequiredErr: {Code: 400, ErrCode: 10078, Description: "consumer config required"},
|
||||||
JSConsumerDuplicateFilterSubjects: {Code: 400, ErrCode: 10136, Description: "consumer cannot have both FilterSubject and FilterSubjects specified"},
|
JSConsumerCreateDurableAndNameMismatch: {Code: 400, ErrCode: 10132, Description: "Consumer Durable and Name have to be equal if both are provided"},
|
||||||
JSConsumerDurableNameNotInSubjectErr: {Code: 400, ErrCode: 10016, Description: "consumer expected to be durable but no durable name set in subject"},
|
JSConsumerCreateErrF: {Code: 500, ErrCode: 10012, Description: "{err}"},
|
||||||
JSConsumerDurableNameNotMatchSubjectErr: {Code: 400, ErrCode: 10017, Description: "consumer name in subject does not match durable name in request"},
|
JSConsumerCreateFilterSubjectMismatchErr: {Code: 400, ErrCode: 10131, Description: "Consumer create request did not match filtered subject from create subject"},
|
||||||
JSConsumerDurableNameNotSetErr: {Code: 400, ErrCode: 10018, Description: "consumer expected to be durable but a durable name was not set"},
|
JSConsumerDeliverCycleErr: {Code: 400, ErrCode: 10081, Description: "consumer deliver subject forms a cycle"},
|
||||||
JSConsumerEmptyFilter: {Code: 400, ErrCode: 10139, Description: "consumer filter in FilterSubjects cannot be empty"},
|
JSConsumerDeliverToWildcardsErr: {Code: 400, ErrCode: 10079, Description: "consumer deliver subject has wildcards"},
|
||||||
JSConsumerEmptyGroupName: {Code: 400, ErrCode: 10161, Description: "Group name cannot be an empty string"},
|
JSConsumerDescriptionTooLongErrF: {Code: 400, ErrCode: 10107, Description: "consumer description is too long, maximum allowed is {max}"},
|
||||||
JSConsumerEphemeralWithDurableInSubjectErr: {Code: 400, ErrCode: 10019, Description: "consumer expected to be ephemeral but detected a durable name set in subject"},
|
JSConsumerDirectRequiresEphemeralErr: {Code: 400, ErrCode: 10091, Description: "consumer direct requires an ephemeral consumer"},
|
||||||
JSConsumerEphemeralWithDurableNameErr: {Code: 400, ErrCode: 10020, Description: "consumer expected to be ephemeral but a durable name was set in request"},
|
JSConsumerDirectRequiresPushErr: {Code: 400, ErrCode: 10090, Description: "consumer direct requires a push based consumer"},
|
||||||
JSConsumerExistingActiveErr: {Code: 400, ErrCode: 10105, Description: "consumer already exists and is still active"},
|
JSConsumerDoesNotExist: {Code: 400, ErrCode: 10149, Description: "consumer does not exist"},
|
||||||
JSConsumerFCRequiresPushErr: {Code: 400, ErrCode: 10089, Description: "consumer flow control requires a push based consumer"},
|
JSConsumerDuplicateFilterSubjects: {Code: 400, ErrCode: 10136, Description: "consumer cannot have both FilterSubject and FilterSubjects specified"},
|
||||||
JSConsumerFilterNotSubsetErr: {Code: 400, ErrCode: 10093, Description: "consumer filter subject is not a valid subset of the interest subjects"},
|
JSConsumerDurableNameNotInSubjectErr: {Code: 400, ErrCode: 10016, Description: "consumer expected to be durable but no durable name set in subject"},
|
||||||
JSConsumerHBRequiresPushErr: {Code: 400, ErrCode: 10088, Description: "consumer idle heartbeat requires a push based consumer"},
|
JSConsumerDurableNameNotMatchSubjectErr: {Code: 400, ErrCode: 10017, Description: "consumer name in subject does not match durable name in request"},
|
||||||
JSConsumerInactiveThresholdExcess: {Code: 400, ErrCode: 10153, Description: "consumer inactive threshold exceeds system limit of {limit}"},
|
JSConsumerDurableNameNotSetErr: {Code: 400, ErrCode: 10018, Description: "consumer expected to be durable but a durable name was not set"},
|
||||||
JSConsumerInvalidDeliverSubject: {Code: 400, ErrCode: 10112, Description: "invalid push consumer deliver subject"},
|
JSConsumerEmptyFilter: {Code: 400, ErrCode: 10139, Description: "consumer filter in FilterSubjects cannot be empty"},
|
||||||
JSConsumerInvalidGroupNameErr: {Code: 400, ErrCode: 10162, Description: "Valid priority group name must match A-Z, a-z, 0-9, -_/=)+ and may not exceed 16 characters"},
|
JSConsumerEmptyGroupName: {Code: 400, ErrCode: 10161, Description: "Group name cannot be an empty string"},
|
||||||
JSConsumerInvalidPolicyErrF: {Code: 400, ErrCode: 10094, Description: "{err}"},
|
JSConsumerEphemeralWithDurableInSubjectErr: {Code: 400, ErrCode: 10019, Description: "consumer expected to be ephemeral but detected a durable name set in subject"},
|
||||||
JSConsumerInvalidPriorityGroupErr: {Code: 400, ErrCode: 10160, Description: "Provided priority group does not exist for this consumer"},
|
JSConsumerEphemeralWithDurableNameErr: {Code: 400, ErrCode: 10020, Description: "consumer expected to be ephemeral but a durable name was set in request"},
|
||||||
JSConsumerInvalidSamplingErrF: {Code: 400, ErrCode: 10095, Description: "failed to parse consumer sampling configuration: {err}"},
|
JSConsumerExistingActiveErr: {Code: 400, ErrCode: 10105, Description: "consumer already exists and is still active"},
|
||||||
JSConsumerMaxDeliverBackoffErr: {Code: 400, ErrCode: 10116, Description: "max deliver is required to be > length of backoff values"},
|
JSConsumerFCRequiresPushErr: {Code: 400, ErrCode: 10089, Description: "consumer flow control requires a push based consumer"},
|
||||||
JSConsumerMaxPendingAckExcessErrF: {Code: 400, ErrCode: 10121, Description: "consumer max ack pending exceeds system limit of {limit}"},
|
JSConsumerFilterNotSubsetErr: {Code: 400, ErrCode: 10093, Description: "consumer filter subject is not a valid subset of the interest subjects"},
|
||||||
JSConsumerMaxPendingAckPolicyRequiredErr: {Code: 400, ErrCode: 10082, Description: "consumer requires ack policy for max ack pending"},
|
JSConsumerHBRequiresPushErr: {Code: 400, ErrCode: 10088, Description: "consumer idle heartbeat requires a push based consumer"},
|
||||||
JSConsumerMaxRequestBatchExceededF: {Code: 400, ErrCode: 10125, Description: "consumer max request batch exceeds server limit of {limit}"},
|
JSConsumerInactiveThresholdExcess: {Code: 400, ErrCode: 10153, Description: "consumer inactive threshold exceeds system limit of {limit}"},
|
||||||
JSConsumerMaxRequestBatchNegativeErr: {Code: 400, ErrCode: 10114, Description: "consumer max request batch needs to be > 0"},
|
JSConsumerInvalidDeliverSubject: {Code: 400, ErrCode: 10112, Description: "invalid push consumer deliver subject"},
|
||||||
JSConsumerMaxRequestExpiresToSmall: {Code: 400, ErrCode: 10115, Description: "consumer max request expires needs to be >= 1ms"},
|
JSConsumerInvalidGroupNameErr: {Code: 400, ErrCode: 10162, Description: "Valid priority group name must match A-Z, a-z, 0-9, -_/=)+ and may not exceed 16 characters"},
|
||||||
JSConsumerMaxWaitingNegativeErr: {Code: 400, ErrCode: 10087, Description: "consumer max waiting needs to be positive"},
|
JSConsumerInvalidPolicyErrF: {Code: 400, ErrCode: 10094, Description: "{err}"},
|
||||||
JSConsumerMetadataLengthErrF: {Code: 400, ErrCode: 10135, Description: "consumer metadata exceeds maximum size of {limit}"},
|
JSConsumerInvalidPriorityGroupErr: {Code: 400, ErrCode: 10160, Description: "Provided priority group does not exist for this consumer"},
|
||||||
JSConsumerMultipleFiltersNotAllowed: {Code: 400, ErrCode: 10137, Description: "consumer with multiple subject filters cannot use subject based API"},
|
JSConsumerInvalidSamplingErrF: {Code: 400, ErrCode: 10095, Description: "failed to parse consumer sampling configuration: {err}"},
|
||||||
JSConsumerNameContainsPathSeparatorsErr: {Code: 400, ErrCode: 10127, Description: "Consumer name can not contain path separators"},
|
JSConsumerMaxDeliverBackoffErr: {Code: 400, ErrCode: 10116, Description: "max deliver is required to be > length of backoff values"},
|
||||||
JSConsumerNameExistErr: {Code: 400, ErrCode: 10013, Description: "consumer name already in use"},
|
JSConsumerMaxPendingAckExcessErrF: {Code: 400, ErrCode: 10121, Description: "consumer max ack pending exceeds system limit of {limit}"},
|
||||||
JSConsumerNameTooLongErrF: {Code: 400, ErrCode: 10102, Description: "consumer name is too long, maximum allowed is {max}"},
|
JSConsumerMaxPendingAckPolicyRequiredErr: {Code: 400, ErrCode: 10082, Description: "consumer requires ack policy for max ack pending"},
|
||||||
JSConsumerNotFoundErr: {Code: 404, ErrCode: 10014, Description: "consumer not found"},
|
JSConsumerMaxRequestBatchExceededF: {Code: 400, ErrCode: 10125, Description: "consumer max request batch exceeds server limit of {limit}"},
|
||||||
JSConsumerOfflineErr: {Code: 500, ErrCode: 10119, Description: "consumer is offline"},
|
JSConsumerMaxRequestBatchNegativeErr: {Code: 400, ErrCode: 10114, Description: "consumer max request batch needs to be > 0"},
|
||||||
JSConsumerOfflineReasonErrF: {Code: 500, ErrCode: 10195, Description: "consumer is offline: {err}"},
|
JSConsumerMaxRequestExpiresTooSmall: {Code: 400, ErrCode: 10115, Description: "consumer max request expires needs to be >= 1ms"},
|
||||||
JSConsumerOnMappedErr: {Code: 400, ErrCode: 10092, Description: "consumer direct on a mapped consumer"},
|
JSConsumerMaxWaitingNegativeErr: {Code: 400, ErrCode: 10087, Description: "consumer max waiting needs to be positive"},
|
||||||
JSConsumerOverlappingSubjectFilters: {Code: 400, ErrCode: 10138, Description: "consumer subject filters cannot overlap"},
|
JSConsumerMetadataLengthErrF: {Code: 400, ErrCode: 10135, Description: "consumer metadata exceeds maximum size of {limit}"},
|
||||||
JSConsumerPriorityPolicyWithoutGroup: {Code: 400, ErrCode: 10159, Description: "Setting PriorityPolicy requires at least one PriorityGroup to be set"},
|
JSConsumerMultipleFiltersNotAllowed: {Code: 400, ErrCode: 10137, Description: "consumer with multiple subject filters cannot use subject based API"},
|
||||||
JSConsumerPullNotDurableErr: {Code: 400, ErrCode: 10085, Description: "consumer in pull mode requires a durable name"},
|
JSConsumerNameContainsPathSeparatorsErr: {Code: 400, ErrCode: 10127, Description: "Consumer name can not contain path separators"},
|
||||||
JSConsumerPullRequiresAckErr: {Code: 400, ErrCode: 10084, Description: "consumer in pull mode requires explicit ack policy on workqueue stream"},
|
JSConsumerNameExistErr: {Code: 400, ErrCode: 10013, Description: "consumer name already in use"},
|
||||||
JSConsumerPullWithRateLimitErr: {Code: 400, ErrCode: 10086, Description: "consumer in pull mode can not have rate limit set"},
|
JSConsumerNameTooLongErrF: {Code: 400, ErrCode: 10102, Description: "consumer name is too long, maximum allowed is {max}"},
|
||||||
JSConsumerPushMaxWaitingErr: {Code: 400, ErrCode: 10080, Description: "consumer in push mode can not set max waiting"},
|
JSConsumerNotFoundErr: {Code: 404, ErrCode: 10014, Description: "consumer not found"},
|
||||||
JSConsumerPushWithPriorityGroupErr: {Code: 400, ErrCode: 10178, Description: "priority groups can not be used with push consumers"},
|
JSConsumerOfflineErr: {Code: 500, ErrCode: 10119, Description: "consumer is offline"},
|
||||||
JSConsumerReplacementWithDifferentNameErr: {Code: 400, ErrCode: 10106, Description: "consumer replacement durable config not the same"},
|
JSConsumerOfflineReasonErrF: {Code: 500, ErrCode: 10195, Description: "consumer is offline: {err}"},
|
||||||
JSConsumerReplicasExceedsStream: {Code: 400, ErrCode: 10126, Description: "consumer config replica count exceeds parent stream"},
|
JSConsumerOnMappedErr: {Code: 400, ErrCode: 10092, Description: "consumer direct on a mapped consumer"},
|
||||||
JSConsumerReplicasShouldMatchStream: {Code: 400, ErrCode: 10134, Description: "consumer config replicas must match interest retention stream's replicas"},
|
JSConsumerOverlappingSubjectFilters: {Code: 400, ErrCode: 10138, Description: "consumer subject filters cannot overlap"},
|
||||||
JSConsumerSmallHeartbeatErr: {Code: 400, ErrCode: 10083, Description: "consumer idle heartbeat needs to be >= 100ms"},
|
JSConsumerPinnedTTLWithoutPriorityPolicyNone: {Code: 400, ErrCode: 10197, Description: "PinnedTTL cannot be set when PriorityPolicy is none"},
|
||||||
JSConsumerStoreFailedErrF: {Code: 500, ErrCode: 10104, Description: "error creating store for consumer: {err}"},
|
JSConsumerPriorityGroupWithPolicyNone: {Code: 400, ErrCode: 10196, Description: "consumer can not have priority groups when policy is none"},
|
||||||
JSConsumerWQConsumerNotDeliverAllErr: {Code: 400, ErrCode: 10101, Description: "consumer must be deliver all on workqueue stream"},
|
JSConsumerPriorityPolicyWithoutGroup: {Code: 400, ErrCode: 10159, Description: "Setting PriorityPolicy requires at least one PriorityGroup to be set"},
|
||||||
JSConsumerWQConsumerNotUniqueErr: {Code: 400, ErrCode: 10100, Description: "filtered consumer not unique on workqueue stream"},
|
JSConsumerPullNotDurableErr: {Code: 400, ErrCode: 10085, Description: "consumer in pull mode requires a durable name"},
|
||||||
JSConsumerWQMultipleUnfilteredErr: {Code: 400, ErrCode: 10099, Description: "multiple non-filtered consumers not allowed on workqueue stream"},
|
JSConsumerPullRequiresAckErr: {Code: 400, ErrCode: 10084, Description: "consumer in pull mode requires explicit ack policy on workqueue stream"},
|
||||||
JSConsumerWQRequiresExplicitAckErr: {Code: 400, ErrCode: 10098, Description: "workqueue stream requires explicit ack"},
|
JSConsumerPullWithRateLimitErr: {Code: 400, ErrCode: 10086, Description: "consumer in pull mode can not have rate limit set"},
|
||||||
JSConsumerWithFlowControlNeedsHeartbeats: {Code: 400, ErrCode: 10108, Description: "consumer with flow control also needs heartbeats"},
|
JSConsumerPushMaxWaitingErr: {Code: 400, ErrCode: 10080, Description: "consumer in push mode can not set max waiting"},
|
||||||
JSInsufficientResourcesErr: {Code: 503, ErrCode: 10023, Description: "insufficient resources"},
|
JSConsumerPushWithPriorityGroupErr: {Code: 400, ErrCode: 10178, Description: "priority groups can not be used with push consumers"},
|
||||||
JSInvalidJSONErr: {Code: 400, ErrCode: 10025, Description: "invalid JSON: {err}"},
|
JSConsumerReplacementWithDifferentNameErr: {Code: 400, ErrCode: 10106, Description: "consumer replacement durable config not the same"},
|
||||||
JSMaximumConsumersLimitErr: {Code: 400, ErrCode: 10026, Description: "maximum consumers limit reached"},
|
JSConsumerReplayPolicyInvalidErr: {Code: 400, ErrCode: 10182, Description: "consumer replay policy invalid"},
|
||||||
JSMaximumStreamsLimitErr: {Code: 400, ErrCode: 10027, Description: "maximum number of streams reached"},
|
JSConsumerReplicasExceedsStream: {Code: 400, ErrCode: 10126, Description: "consumer config replica count exceeds parent stream"},
|
||||||
JSMemoryResourcesExceededErr: {Code: 500, ErrCode: 10028, Description: "insufficient memory resources available"},
|
JSConsumerReplicasShouldMatchStream: {Code: 400, ErrCode: 10134, Description: "consumer config replicas must match interest retention stream's replicas"},
|
||||||
JSMessageTTLDisabledErr: {Code: 400, ErrCode: 10166, Description: "per-message TTL is disabled"},
|
JSConsumerSmallHeartbeatErr: {Code: 400, ErrCode: 10083, Description: "consumer idle heartbeat needs to be >= 100ms"},
|
||||||
JSMessageTTLInvalidErr: {Code: 400, ErrCode: 10165, Description: "invalid per-message TTL"},
|
JSConsumerStoreFailedErrF: {Code: 500, ErrCode: 10104, Description: "error creating store for consumer: {err}"},
|
||||||
JSMirrorConsumerSetupFailedErrF: {Code: 500, ErrCode: 10029, Description: "{err}"},
|
JSConsumerWQConsumerNotDeliverAllErr: {Code: 400, ErrCode: 10101, Description: "consumer must be deliver all on workqueue stream"},
|
||||||
JSMirrorInvalidStreamName: {Code: 400, ErrCode: 10142, Description: "mirrored stream name is invalid"},
|
JSConsumerWQConsumerNotUniqueErr: {Code: 400, ErrCode: 10100, Description: "filtered consumer not unique on workqueue stream"},
|
||||||
JSMirrorInvalidSubjectFilter: {Code: 400, ErrCode: 10151, Description: "mirror transform source: {err}"},
|
JSConsumerWQMultipleUnfilteredErr: {Code: 400, ErrCode: 10099, Description: "multiple non-filtered consumers not allowed on workqueue stream"},
|
||||||
JSMirrorInvalidTransformDestination: {Code: 400, ErrCode: 10154, Description: "mirror transform: {err}"},
|
JSConsumerWQRequiresExplicitAckErr: {Code: 400, ErrCode: 10098, Description: "workqueue stream requires explicit ack"},
|
||||||
JSMirrorMaxMessageSizeTooBigErr: {Code: 400, ErrCode: 10030, Description: "stream mirror must have max message size >= source"},
|
JSConsumerWithFlowControlNeedsHeartbeats: {Code: 400, ErrCode: 10108, Description: "consumer with flow control also needs heartbeats"},
|
||||||
JSMirrorMultipleFiltersNotAllowed: {Code: 400, ErrCode: 10150, Description: "mirror with multiple subject transforms cannot also have a single subject filter"},
|
JSInsufficientResourcesErr: {Code: 503, ErrCode: 10023, Description: "insufficient resources"},
|
||||||
JSMirrorOverlappingSubjectFilters: {Code: 400, ErrCode: 10152, Description: "mirror subject filters can not overlap"},
|
JSInvalidJSONErr: {Code: 400, ErrCode: 10025, Description: "invalid JSON: {err}"},
|
||||||
JSMirrorWithFirstSeqErr: {Code: 400, ErrCode: 10143, Description: "stream mirrors can not have first sequence configured"},
|
JSMaximumConsumersLimitErr: {Code: 400, ErrCode: 10026, Description: "maximum consumers limit reached"},
|
||||||
JSMirrorWithSourcesErr: {Code: 400, ErrCode: 10031, Description: "stream mirrors can not also contain other sources"},
|
JSMaximumStreamsLimitErr: {Code: 400, ErrCode: 10027, Description: "maximum number of streams reached"},
|
||||||
JSMirrorWithStartSeqAndTimeErr: {Code: 400, ErrCode: 10032, Description: "stream mirrors can not have both start seq and start time configured"},
|
JSMemoryResourcesExceededErr: {Code: 500, ErrCode: 10028, Description: "insufficient memory resources available"},
|
||||||
JSMirrorWithSubjectFiltersErr: {Code: 400, ErrCode: 10033, Description: "stream mirrors can not contain filtered subjects"},
|
JSMessageCounterBrokenErr: {Code: 400, ErrCode: 10172, Description: "message counter is broken"},
|
||||||
JSMirrorWithSubjectsErr: {Code: 400, ErrCode: 10034, Description: "stream mirrors can not contain subjects"},
|
JSMessageIncrDisabledErr: {Code: 400, ErrCode: 10168, Description: "message counters is disabled"},
|
||||||
JSNoAccountErr: {Code: 503, ErrCode: 10035, Description: "account not found"},
|
JSMessageIncrInvalidErr: {Code: 400, ErrCode: 10171, Description: "message counter increment is invalid"},
|
||||||
JSNoLimitsErr: {Code: 400, ErrCode: 10120, Description: "no JetStream default or applicable tiered limit present"},
|
JSMessageIncrMissingErr: {Code: 400, ErrCode: 10169, Description: "message counter increment is missing"},
|
||||||
JSNoMessageFoundErr: {Code: 404, ErrCode: 10037, Description: "no message found"},
|
JSMessageIncrPayloadErr: {Code: 400, ErrCode: 10170, Description: "message counter has payload"},
|
||||||
JSNotEmptyRequestErr: {Code: 400, ErrCode: 10038, Description: "expected an empty request payload"},
|
JSMessageSchedulesDisabledErr: {Code: 400, ErrCode: 10188, Description: "message schedules is disabled"},
|
||||||
JSNotEnabledErr: {Code: 503, ErrCode: 10076, Description: "JetStream not enabled"},
|
JSMessageSchedulesPatternInvalidErr: {Code: 400, ErrCode: 10189, Description: "message schedules pattern is invalid"},
|
||||||
JSNotEnabledForAccountErr: {Code: 503, ErrCode: 10039, Description: "JetStream not enabled for account"},
|
JSMessageSchedulesRollupInvalidErr: {Code: 400, ErrCode: 10192, Description: "message schedules invalid rollup"},
|
||||||
JSPedanticErrF: {Code: 400, ErrCode: 10157, Description: "pedantic mode: {err}"},
|
JSMessageSchedulesTTLInvalidErr: {Code: 400, ErrCode: 10191, Description: "message schedules invalid per-message TTL"},
|
||||||
JSPeerRemapErr: {Code: 503, ErrCode: 10075, Description: "peer remap failed"},
|
JSMessageSchedulesTargetInvalidErr: {Code: 400, ErrCode: 10190, Description: "message schedules target is invalid"},
|
||||||
JSRaftGeneralErrF: {Code: 500, ErrCode: 10041, Description: "{err}"},
|
JSMessageTTLDisabledErr: {Code: 400, ErrCode: 10166, Description: "per-message TTL is disabled"},
|
||||||
JSReplicasCountCannotBeNegative: {Code: 400, ErrCode: 10133, Description: "replicas count cannot be negative"},
|
JSMessageTTLInvalidErr: {Code: 400, ErrCode: 10165, Description: "invalid per-message TTL"},
|
||||||
JSRestoreSubscribeFailedErrF: {Code: 500, ErrCode: 10042, Description: "JetStream unable to subscribe to restore snapshot {subject}: {err}"},
|
JSMirrorConsumerSetupFailedErrF: {Code: 500, ErrCode: 10029, Description: "{err}"},
|
||||||
JSSequenceNotFoundErrF: {Code: 400, ErrCode: 10043, Description: "sequence {seq} not found"},
|
JSMirrorInvalidStreamName: {Code: 400, ErrCode: 10142, Description: "mirrored stream name is invalid"},
|
||||||
JSSnapshotDeliverSubjectInvalidErr: {Code: 400, ErrCode: 10015, Description: "deliver subject not valid"},
|
JSMirrorInvalidSubjectFilter: {Code: 400, ErrCode: 10151, Description: "mirror transform source: {err}"},
|
||||||
JSSourceConsumerSetupFailedErrF: {Code: 500, ErrCode: 10045, Description: "{err}"},
|
JSMirrorInvalidTransformDestination: {Code: 400, ErrCode: 10154, Description: "mirror transform: {err}"},
|
||||||
JSSourceDuplicateDetected: {Code: 400, ErrCode: 10140, Description: "duplicate source configuration detected"},
|
JSMirrorMaxMessageSizeTooBigErr: {Code: 400, ErrCode: 10030, Description: "stream mirror must have max message size >= source"},
|
||||||
JSSourceInvalidStreamName: {Code: 400, ErrCode: 10141, Description: "sourced stream name is invalid"},
|
JSMirrorMultipleFiltersNotAllowed: {Code: 400, ErrCode: 10150, Description: "mirror with multiple subject transforms cannot also have a single subject filter"},
|
||||||
JSSourceInvalidSubjectFilter: {Code: 400, ErrCode: 10145, Description: "source transform source: {err}"},
|
JSMirrorOverlappingSubjectFilters: {Code: 400, ErrCode: 10152, Description: "mirror subject filters can not overlap"},
|
||||||
JSSourceInvalidTransformDestination: {Code: 400, ErrCode: 10146, Description: "source transform: {err}"},
|
JSMirrorWithAtomicPublishErr: {Code: 400, ErrCode: 10198, Description: "stream mirrors can not also use atomic publishing"},
|
||||||
JSSourceMaxMessageSizeTooBigErr: {Code: 400, ErrCode: 10046, Description: "stream source must have max message size >= target"},
|
JSMirrorWithCountersErr: {Code: 400, ErrCode: 10173, Description: "stream mirrors can not also calculate counters"},
|
||||||
JSSourceMultipleFiltersNotAllowed: {Code: 400, ErrCode: 10144, Description: "source with multiple subject transforms cannot also have a single subject filter"},
|
JSMirrorWithFirstSeqErr: {Code: 400, ErrCode: 10143, Description: "stream mirrors can not have first sequence configured"},
|
||||||
JSSourceOverlappingSubjectFilters: {Code: 400, ErrCode: 10147, Description: "source filters can not overlap"},
|
JSMirrorWithMsgSchedulesErr: {Code: 400, ErrCode: 10186, Description: "stream mirrors can not also schedule messages"},
|
||||||
JSStorageResourcesExceededErr: {Code: 500, ErrCode: 10047, Description: "insufficient storage resources available"},
|
JSMirrorWithSourcesErr: {Code: 400, ErrCode: 10031, Description: "stream mirrors can not also contain other sources"},
|
||||||
JSStreamAssignmentErrF: {Code: 500, ErrCode: 10048, Description: "{err}"},
|
JSMirrorWithStartSeqAndTimeErr: {Code: 400, ErrCode: 10032, Description: "stream mirrors can not have both start seq and start time configured"},
|
||||||
JSStreamCreateErrF: {Code: 500, ErrCode: 10049, Description: "{err}"},
|
JSMirrorWithSubjectFiltersErr: {Code: 400, ErrCode: 10033, Description: "stream mirrors can not contain filtered subjects"},
|
||||||
JSStreamDeleteErrF: {Code: 500, ErrCode: 10050, Description: "{err}"},
|
JSMirrorWithSubjectsErr: {Code: 400, ErrCode: 10034, Description: "stream mirrors can not contain subjects"},
|
||||||
JSStreamDuplicateMessageConflict: {Code: 409, ErrCode: 10158, Description: "duplicate message id is in process"},
|
JSNoAccountErr: {Code: 503, ErrCode: 10035, Description: "account not found"},
|
||||||
JSStreamExpectedLastSeqPerSubjectNotReady: {Code: 503, ErrCode: 10163, Description: "expected last sequence per subject temporarily unavailable"},
|
JSNoLimitsErr: {Code: 400, ErrCode: 10120, Description: "no JetStream default or applicable tiered limit present"},
|
||||||
JSStreamExternalApiOverlapErrF: {Code: 400, ErrCode: 10021, Description: "stream external api prefix {prefix} must not overlap with {subject}"},
|
JSNoMessageFoundErr: {Code: 404, ErrCode: 10037, Description: "no message found"},
|
||||||
JSStreamExternalDelPrefixOverlapsErrF: {Code: 400, ErrCode: 10022, Description: "stream external delivery prefix {prefix} overlaps with stream subject {subject}"},
|
JSNotEmptyRequestErr: {Code: 400, ErrCode: 10038, Description: "expected an empty request payload"},
|
||||||
JSStreamGeneralErrorF: {Code: 500, ErrCode: 10051, Description: "{err}"},
|
JSNotEnabledErr: {Code: 503, ErrCode: 10076, Description: "JetStream not enabled"},
|
||||||
JSStreamHeaderExceedsMaximumErr: {Code: 400, ErrCode: 10097, Description: "header size exceeds maximum allowed of 64k"},
|
JSNotEnabledForAccountErr: {Code: 503, ErrCode: 10039, Description: "JetStream not enabled for account"},
|
||||||
JSStreamInfoMaxSubjectsErr: {Code: 500, ErrCode: 10117, Description: "subject details would exceed maximum allowed"},
|
JSPedanticErrF: {Code: 400, ErrCode: 10157, Description: "pedantic mode: {err}"},
|
||||||
JSStreamInvalidConfigF: {Code: 500, ErrCode: 10052, Description: "{err}"},
|
JSPeerRemapErr: {Code: 503, ErrCode: 10075, Description: "peer remap failed"},
|
||||||
JSStreamInvalidErr: {Code: 500, ErrCode: 10096, Description: "stream not valid"},
|
JSRaftGeneralErrF: {Code: 500, ErrCode: 10041, Description: "{err}"},
|
||||||
JSStreamInvalidExternalDeliverySubjErrF: {Code: 400, ErrCode: 10024, Description: "stream external delivery prefix {prefix} must not contain wildcards"},
|
JSReplicasCountCannotBeNegative: {Code: 400, ErrCode: 10133, Description: "replicas count cannot be negative"},
|
||||||
JSStreamLimitsErrF: {Code: 500, ErrCode: 10053, Description: "{err}"},
|
JSRequiredApiLevelErr: {Code: 412, ErrCode: 10185, Description: "JetStream minimum api level required"},
|
||||||
JSStreamMaxBytesRequired: {Code: 400, ErrCode: 10113, Description: "account requires a stream config to have max bytes set"},
|
JSRestoreSubscribeFailedErrF: {Code: 500, ErrCode: 10042, Description: "JetStream unable to subscribe to restore snapshot {subject}: {err}"},
|
||||||
JSStreamMaxStreamBytesExceeded: {Code: 400, ErrCode: 10122, Description: "stream max bytes exceeds account limit max stream bytes"},
|
JSSequenceNotFoundErrF: {Code: 400, ErrCode: 10043, Description: "sequence {seq} not found"},
|
||||||
JSStreamMessageExceedsMaximumErr: {Code: 400, ErrCode: 10054, Description: "message size exceeds maximum allowed"},
|
JSSnapshotDeliverSubjectInvalidErr: {Code: 400, ErrCode: 10015, Description: "deliver subject not valid"},
|
||||||
JSStreamMirrorNotUpdatableErr: {Code: 400, ErrCode: 10055, Description: "stream mirror configuration can not be updated"},
|
JSSourceConsumerSetupFailedErrF: {Code: 500, ErrCode: 10045, Description: "{err}"},
|
||||||
JSStreamMismatchErr: {Code: 400, ErrCode: 10056, Description: "stream name in subject does not match request"},
|
JSSourceDuplicateDetected: {Code: 400, ErrCode: 10140, Description: "duplicate source configuration detected"},
|
||||||
JSStreamMoveAndScaleErr: {Code: 400, ErrCode: 10123, Description: "can not move and scale a stream in a single update"},
|
JSSourceInvalidStreamName: {Code: 400, ErrCode: 10141, Description: "sourced stream name is invalid"},
|
||||||
JSStreamMoveInProgressF: {Code: 400, ErrCode: 10124, Description: "stream move already in progress: {msg}"},
|
JSSourceInvalidSubjectFilter: {Code: 400, ErrCode: 10145, Description: "source transform source: {err}"},
|
||||||
JSStreamMoveNotInProgress: {Code: 400, ErrCode: 10129, Description: "stream move not in progress"},
|
JSSourceInvalidTransformDestination: {Code: 400, ErrCode: 10146, Description: "source transform: {err}"},
|
||||||
JSStreamMsgDeleteFailedF: {Code: 500, ErrCode: 10057, Description: "{err}"},
|
JSSourceMaxMessageSizeTooBigErr: {Code: 400, ErrCode: 10046, Description: "stream source must have max message size >= target"},
|
||||||
JSStreamNameContainsPathSeparatorsErr: {Code: 400, ErrCode: 10128, Description: "Stream name can not contain path separators"},
|
JSSourceMultipleFiltersNotAllowed: {Code: 400, ErrCode: 10144, Description: "source with multiple subject transforms cannot also have a single subject filter"},
|
||||||
JSStreamNameExistErr: {Code: 400, ErrCode: 10058, Description: "stream name already in use with a different configuration"},
|
JSSourceOverlappingSubjectFilters: {Code: 400, ErrCode: 10147, Description: "source filters can not overlap"},
|
||||||
JSStreamNameExistRestoreFailedErr: {Code: 400, ErrCode: 10130, Description: "stream name already in use, cannot restore"},
|
JSSourceWithMsgSchedulesErr: {Code: 400, ErrCode: 10187, Description: "stream source can not also schedule messages"},
|
||||||
JSStreamNotFoundErr: {Code: 404, ErrCode: 10059, Description: "stream not found"},
|
JSStorageResourcesExceededErr: {Code: 500, ErrCode: 10047, Description: "insufficient storage resources available"},
|
||||||
JSStreamNotMatchErr: {Code: 400, ErrCode: 10060, Description: "expected stream does not match"},
|
JSStreamAssignmentErrF: {Code: 500, ErrCode: 10048, Description: "{err}"},
|
||||||
JSStreamOfflineErr: {Code: 500, ErrCode: 10118, Description: "stream is offline"},
|
JSStreamCreateErrF: {Code: 500, ErrCode: 10049, Description: "{err}"},
|
||||||
JSStreamOfflineReasonErrF: {Code: 500, ErrCode: 10194, Description: "stream is offline: {err}"},
|
JSStreamDeleteErrF: {Code: 500, ErrCode: 10050, Description: "{err}"},
|
||||||
JSStreamPurgeFailedF: {Code: 500, ErrCode: 10110, Description: "{err}"},
|
JSStreamDuplicateMessageConflict: {Code: 409, ErrCode: 10158, Description: "duplicate message id is in process"},
|
||||||
JSStreamReplicasNotSupportedErr: {Code: 500, ErrCode: 10074, Description: "replicas > 1 not supported in non-clustered mode"},
|
JSStreamExpectedLastSeqPerSubjectInvalid: {Code: 400, ErrCode: 10193, Description: "missing sequence for expected last sequence per subject"},
|
||||||
JSStreamReplicasNotUpdatableErr: {Code: 400, ErrCode: 10061, Description: "Replicas configuration can not be updated"},
|
JSStreamExpectedLastSeqPerSubjectNotReady: {Code: 503, ErrCode: 10163, Description: "expected last sequence per subject temporarily unavailable"},
|
||||||
JSStreamRestoreErrF: {Code: 500, ErrCode: 10062, Description: "restore failed: {err}"},
|
JSStreamExternalApiOverlapErrF: {Code: 400, ErrCode: 10021, Description: "stream external api prefix {prefix} must not overlap with {subject}"},
|
||||||
JSStreamRollupFailedF: {Code: 500, ErrCode: 10111, Description: "{err}"},
|
JSStreamExternalDelPrefixOverlapsErrF: {Code: 400, ErrCode: 10022, Description: "stream external delivery prefix {prefix} overlaps with stream subject {subject}"},
|
||||||
JSStreamSealedErr: {Code: 400, ErrCode: 10109, Description: "invalid operation on sealed stream"},
|
JSStreamGeneralErrorF: {Code: 500, ErrCode: 10051, Description: "{err}"},
|
||||||
JSStreamSequenceNotMatchErr: {Code: 503, ErrCode: 10063, Description: "expected stream sequence does not match"},
|
JSStreamHeaderExceedsMaximumErr: {Code: 400, ErrCode: 10097, Description: "header size exceeds maximum allowed of 64k"},
|
||||||
JSStreamSnapshotErrF: {Code: 500, ErrCode: 10064, Description: "snapshot failed: {err}"},
|
JSStreamInfoMaxSubjectsErr: {Code: 500, ErrCode: 10117, Description: "subject details would exceed maximum allowed"},
|
||||||
JSStreamStoreFailedF: {Code: 503, ErrCode: 10077, Description: "{err}"},
|
JSStreamInvalidConfigF: {Code: 500, ErrCode: 10052, Description: "{err}"},
|
||||||
JSStreamSubjectOverlapErr: {Code: 400, ErrCode: 10065, Description: "subjects overlap with an existing stream"},
|
JSStreamInvalidErr: {Code: 500, ErrCode: 10096, Description: "stream not valid"},
|
||||||
JSStreamTemplateCreateErrF: {Code: 500, ErrCode: 10066, Description: "{err}"},
|
JSStreamInvalidExternalDeliverySubjErrF: {Code: 400, ErrCode: 10024, Description: "stream external delivery prefix {prefix} must not contain wildcards"},
|
||||||
JSStreamTemplateDeleteErrF: {Code: 500, ErrCode: 10067, Description: "{err}"},
|
JSStreamLimitsErrF: {Code: 500, ErrCode: 10053, Description: "{err}"},
|
||||||
JSStreamTemplateNotFoundErr: {Code: 404, ErrCode: 10068, Description: "template not found"},
|
JSStreamMaxBytesRequired: {Code: 400, ErrCode: 10113, Description: "account requires a stream config to have max bytes set"},
|
||||||
JSStreamTooManyRequests: {Code: 429, ErrCode: 10167, Description: "too many requests"},
|
JSStreamMaxStreamBytesExceeded: {Code: 400, ErrCode: 10122, Description: "stream max bytes exceeds account limit max stream bytes"},
|
||||||
JSStreamTransformInvalidDestination: {Code: 400, ErrCode: 10156, Description: "stream transform: {err}"},
|
JSStreamMessageExceedsMaximumErr: {Code: 400, ErrCode: 10054, Description: "message size exceeds maximum allowed"},
|
||||||
JSStreamTransformInvalidSource: {Code: 400, ErrCode: 10155, Description: "stream transform source: {err}"},
|
JSStreamMinLastSeqErr: {Code: 412, ErrCode: 10180, Description: "min last sequence"},
|
||||||
JSStreamUpdateErrF: {Code: 500, ErrCode: 10069, Description: "{err}"},
|
JSStreamMirrorNotUpdatableErr: {Code: 400, ErrCode: 10055, Description: "stream mirror configuration can not be updated"},
|
||||||
JSStreamWrongLastMsgIDErrF: {Code: 400, ErrCode: 10070, Description: "wrong last msg ID: {id}"},
|
JSStreamMismatchErr: {Code: 400, ErrCode: 10056, Description: "stream name in subject does not match request"},
|
||||||
JSStreamWrongLastSequenceConstantErr: {Code: 400, ErrCode: 10164, Description: "wrong last sequence"},
|
JSStreamMoveAndScaleErr: {Code: 400, ErrCode: 10123, Description: "can not move and scale a stream in a single update"},
|
||||||
JSStreamWrongLastSequenceErrF: {Code: 400, ErrCode: 10071, Description: "wrong last sequence: {seq}"},
|
JSStreamMoveInProgressF: {Code: 400, ErrCode: 10124, Description: "stream move already in progress: {msg}"},
|
||||||
JSTempStorageFailedErr: {Code: 500, ErrCode: 10072, Description: "JetStream unable to open temp storage for restore"},
|
JSStreamMoveNotInProgress: {Code: 400, ErrCode: 10129, Description: "stream move not in progress"},
|
||||||
JSTemplateNameNotMatchSubjectErr: {Code: 400, ErrCode: 10073, Description: "template name in subject does not match request"},
|
JSStreamMsgDeleteFailedF: {Code: 500, ErrCode: 10057, Description: "{err}"},
|
||||||
|
JSStreamNameContainsPathSeparatorsErr: {Code: 400, ErrCode: 10128, Description: "Stream name can not contain path separators"},
|
||||||
|
JSStreamNameExistErr: {Code: 400, ErrCode: 10058, Description: "stream name already in use with a different configuration"},
|
||||||
|
JSStreamNameExistRestoreFailedErr: {Code: 400, ErrCode: 10130, Description: "stream name already in use, cannot restore"},
|
||||||
|
JSStreamNotFoundErr: {Code: 404, ErrCode: 10059, Description: "stream not found"},
|
||||||
|
JSStreamNotMatchErr: {Code: 400, ErrCode: 10060, Description: "expected stream does not match"},
|
||||||
|
JSStreamOfflineErr: {Code: 500, ErrCode: 10118, Description: "stream is offline"},
|
||||||
|
JSStreamOfflineReasonErrF: {Code: 500, ErrCode: 10194, Description: "stream is offline: {err}"},
|
||||||
|
JSStreamPurgeFailedF: {Code: 500, ErrCode: 10110, Description: "{err}"},
|
||||||
|
JSStreamReplicasNotSupportedErr: {Code: 500, ErrCode: 10074, Description: "replicas > 1 not supported in non-clustered mode"},
|
||||||
|
JSStreamReplicasNotUpdatableErr: {Code: 400, ErrCode: 10061, Description: "Replicas configuration can not be updated"},
|
||||||
|
JSStreamRestoreErrF: {Code: 500, ErrCode: 10062, Description: "restore failed: {err}"},
|
||||||
|
JSStreamRollupFailedF: {Code: 500, ErrCode: 10111, Description: "{err}"},
|
||||||
|
JSStreamSealedErr: {Code: 400, ErrCode: 10109, Description: "invalid operation on sealed stream"},
|
||||||
|
JSStreamSequenceNotMatchErr: {Code: 503, ErrCode: 10063, Description: "expected stream sequence does not match"},
|
||||||
|
JSStreamSnapshotErrF: {Code: 500, ErrCode: 10064, Description: "snapshot failed: {err}"},
|
||||||
|
JSStreamStoreFailedF: {Code: 503, ErrCode: 10077, Description: "{err}"},
|
||||||
|
JSStreamSubjectOverlapErr: {Code: 400, ErrCode: 10065, Description: "subjects overlap with an existing stream"},
|
||||||
|
JSStreamTemplateCreateErrF: {Code: 500, ErrCode: 10066, Description: "{err}"},
|
||||||
|
JSStreamTemplateDeleteErrF: {Code: 500, ErrCode: 10067, Description: "{err}"},
|
||||||
|
JSStreamTemplateNotFoundErr: {Code: 404, ErrCode: 10068, Description: "template not found"},
|
||||||
|
JSStreamTooManyRequests: {Code: 429, ErrCode: 10167, Description: "too many requests"},
|
||||||
|
JSStreamTransformInvalidDestination: {Code: 400, ErrCode: 10156, Description: "stream transform: {err}"},
|
||||||
|
JSStreamTransformInvalidSource: {Code: 400, ErrCode: 10155, Description: "stream transform source: {err}"},
|
||||||
|
JSStreamUpdateErrF: {Code: 500, ErrCode: 10069, Description: "{err}"},
|
||||||
|
JSStreamWrongLastMsgIDErrF: {Code: 400, ErrCode: 10070, Description: "wrong last msg ID: {id}"},
|
||||||
|
JSStreamWrongLastSequenceConstantErr: {Code: 400, ErrCode: 10164, Description: "wrong last sequence"},
|
||||||
|
JSStreamWrongLastSequenceErrF: {Code: 400, ErrCode: 10071, Description: "wrong last sequence: {seq}"},
|
||||||
|
JSTempStorageFailedErr: {Code: 500, ErrCode: 10072, Description: "JetStream unable to open temp storage for restore"},
|
||||||
|
JSTemplateNameNotMatchSubjectErr: {Code: 400, ErrCode: 10073, Description: "template name in subject does not match request"},
|
||||||
}
|
}
|
||||||
// ErrJetStreamNotClustered Deprecated by JSClusterNotActiveErr ApiError, use IsNatsError() for comparisons
|
// ErrJetStreamNotClustered Deprecated by JSClusterNotActiveErr ApiError, use IsNatsError() for comparisons
|
||||||
ErrJetStreamNotClustered = ApiErrors[JSClusterNotActiveErr]
|
ErrJetStreamNotClustered = ApiErrors[JSClusterNotActiveErr]
|
||||||
@@ -719,6 +835,78 @@ func NewJSAccountResourcesExceededError(opts ...ErrorOption) *ApiError {
|
|||||||
return ApiErrors[JSAccountResourcesExceededErr]
|
return ApiErrors[JSAccountResourcesExceededErr]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewJSAtomicPublishDisabledError creates a new JSAtomicPublishDisabledErr error: "atomic publish is disabled"
|
||||||
|
func NewJSAtomicPublishDisabledError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSAtomicPublishDisabledErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSAtomicPublishIncompleteBatchError creates a new JSAtomicPublishIncompleteBatchErr error: "atomic publish batch is incomplete"
|
||||||
|
func NewJSAtomicPublishIncompleteBatchError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSAtomicPublishIncompleteBatchErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSAtomicPublishInvalidBatchIDError creates a new JSAtomicPublishInvalidBatchIDErr error: "atomic publish batch ID is invalid"
|
||||||
|
func NewJSAtomicPublishInvalidBatchIDError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSAtomicPublishInvalidBatchIDErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSAtomicPublishMissingSeqError creates a new JSAtomicPublishMissingSeqErr error: "atomic publish sequence is missing"
|
||||||
|
func NewJSAtomicPublishMissingSeqError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSAtomicPublishMissingSeqErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSAtomicPublishTooLargeBatchError creates a new JSAtomicPublishTooLargeBatchErrF error: "atomic publish batch is too large: {size}"
|
||||||
|
func NewJSAtomicPublishTooLargeBatchError(size interface{}, opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
e := ApiErrors[JSAtomicPublishTooLargeBatchErrF]
|
||||||
|
args := e.toReplacerArgs([]interface{}{"{size}", size})
|
||||||
|
return &ApiError{
|
||||||
|
Code: e.Code,
|
||||||
|
ErrCode: e.ErrCode,
|
||||||
|
Description: strings.NewReplacer(args...).Replace(e.Description),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSAtomicPublishUnsupportedHeaderBatchError creates a new JSAtomicPublishUnsupportedHeaderBatchErr error: "atomic publish unsupported header used: {header}"
|
||||||
|
func NewJSAtomicPublishUnsupportedHeaderBatchError(header interface{}, opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
e := ApiErrors[JSAtomicPublishUnsupportedHeaderBatchErr]
|
||||||
|
args := e.toReplacerArgs([]interface{}{"{header}", header})
|
||||||
|
return &ApiError{
|
||||||
|
Code: e.Code,
|
||||||
|
ErrCode: e.ErrCode,
|
||||||
|
Description: strings.NewReplacer(args...).Replace(e.Description),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewJSBadRequestError creates a new JSBadRequestErr error: "bad request"
|
// NewJSBadRequestError creates a new JSBadRequestErr error: "bad request"
|
||||||
func NewJSBadRequestError(opts ...ErrorOption) *ApiError {
|
func NewJSBadRequestError(opts ...ErrorOption) *ApiError {
|
||||||
eopts := parseOpts(opts)
|
eopts := parseOpts(opts)
|
||||||
@@ -845,6 +1033,26 @@ func NewJSClusterUnSupportFeatureError(opts ...ErrorOption) *ApiError {
|
|||||||
return ApiErrors[JSClusterUnSupportFeatureErr]
|
return ApiErrors[JSClusterUnSupportFeatureErr]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewJSConsumerAckPolicyInvalidError creates a new JSConsumerAckPolicyInvalidErr error: "consumer ack policy invalid"
|
||||||
|
func NewJSConsumerAckPolicyInvalidError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSConsumerAckPolicyInvalidErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSConsumerAckWaitNegativeError creates a new JSConsumerAckWaitNegativeErr error: "consumer ack wait needs to be positive"
|
||||||
|
func NewJSConsumerAckWaitNegativeError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSConsumerAckWaitNegativeErr]
|
||||||
|
}
|
||||||
|
|
||||||
// NewJSConsumerAlreadyExistsError creates a new JSConsumerAlreadyExists error: "consumer already exists"
|
// NewJSConsumerAlreadyExistsError creates a new JSConsumerAlreadyExists error: "consumer already exists"
|
||||||
func NewJSConsumerAlreadyExistsError(opts ...ErrorOption) *ApiError {
|
func NewJSConsumerAlreadyExistsError(opts ...ErrorOption) *ApiError {
|
||||||
eopts := parseOpts(opts)
|
eopts := parseOpts(opts)
|
||||||
@@ -855,6 +1063,16 @@ func NewJSConsumerAlreadyExistsError(opts ...ErrorOption) *ApiError {
|
|||||||
return ApiErrors[JSConsumerAlreadyExists]
|
return ApiErrors[JSConsumerAlreadyExists]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewJSConsumerBackOffNegativeError creates a new JSConsumerBackOffNegativeErr error: "consumer backoff needs to be positive"
|
||||||
|
func NewJSConsumerBackOffNegativeError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSConsumerBackOffNegativeErr]
|
||||||
|
}
|
||||||
|
|
||||||
// NewJSConsumerBadDurableNameError creates a new JSConsumerBadDurableNameErr error: "durable name can not contain '.', '*', '>'"
|
// NewJSConsumerBadDurableNameError creates a new JSConsumerBadDurableNameErr error: "durable name can not contain '.', '*', '>'"
|
||||||
func NewJSConsumerBadDurableNameError(opts ...ErrorOption) *ApiError {
|
func NewJSConsumerBadDurableNameError(opts ...ErrorOption) *ApiError {
|
||||||
eopts := parseOpts(opts)
|
eopts := parseOpts(opts)
|
||||||
@@ -1237,14 +1455,14 @@ func NewJSConsumerMaxRequestBatchNegativeError(opts ...ErrorOption) *ApiError {
|
|||||||
return ApiErrors[JSConsumerMaxRequestBatchNegativeErr]
|
return ApiErrors[JSConsumerMaxRequestBatchNegativeErr]
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewJSConsumerMaxRequestExpiresToSmallError creates a new JSConsumerMaxRequestExpiresToSmall error: "consumer max request expires needs to be >= 1ms"
|
// NewJSConsumerMaxRequestExpiresTooSmallError creates a new JSConsumerMaxRequestExpiresTooSmall error: "consumer max request expires needs to be >= 1ms"
|
||||||
func NewJSConsumerMaxRequestExpiresToSmallError(opts ...ErrorOption) *ApiError {
|
func NewJSConsumerMaxRequestExpiresTooSmallError(opts ...ErrorOption) *ApiError {
|
||||||
eopts := parseOpts(opts)
|
eopts := parseOpts(opts)
|
||||||
if ae, ok := eopts.err.(*ApiError); ok {
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
return ae
|
return ae
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApiErrors[JSConsumerMaxRequestExpiresToSmall]
|
return ApiErrors[JSConsumerMaxRequestExpiresTooSmall]
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewJSConsumerMaxWaitingNegativeError creates a new JSConsumerMaxWaitingNegativeErr error: "consumer max waiting needs to be positive"
|
// NewJSConsumerMaxWaitingNegativeError creates a new JSConsumerMaxWaitingNegativeErr error: "consumer max waiting needs to be positive"
|
||||||
@@ -1375,6 +1593,26 @@ func NewJSConsumerOverlappingSubjectFiltersError(opts ...ErrorOption) *ApiError
|
|||||||
return ApiErrors[JSConsumerOverlappingSubjectFilters]
|
return ApiErrors[JSConsumerOverlappingSubjectFilters]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewJSConsumerPinnedTTLWithoutPriorityPolicyNoneError creates a new JSConsumerPinnedTTLWithoutPriorityPolicyNone error: "PinnedTTL cannot be set when PriorityPolicy is none"
|
||||||
|
func NewJSConsumerPinnedTTLWithoutPriorityPolicyNoneError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSConsumerPinnedTTLWithoutPriorityPolicyNone]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSConsumerPriorityGroupWithPolicyNoneError creates a new JSConsumerPriorityGroupWithPolicyNone error: "consumer can not have priority groups when policy is none"
|
||||||
|
func NewJSConsumerPriorityGroupWithPolicyNoneError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSConsumerPriorityGroupWithPolicyNone]
|
||||||
|
}
|
||||||
|
|
||||||
// NewJSConsumerPriorityPolicyWithoutGroupError creates a new JSConsumerPriorityPolicyWithoutGroup error: "Setting PriorityPolicy requires at least one PriorityGroup to be set"
|
// NewJSConsumerPriorityPolicyWithoutGroupError creates a new JSConsumerPriorityPolicyWithoutGroup error: "Setting PriorityPolicy requires at least one PriorityGroup to be set"
|
||||||
func NewJSConsumerPriorityPolicyWithoutGroupError(opts ...ErrorOption) *ApiError {
|
func NewJSConsumerPriorityPolicyWithoutGroupError(opts ...ErrorOption) *ApiError {
|
||||||
eopts := parseOpts(opts)
|
eopts := parseOpts(opts)
|
||||||
@@ -1445,6 +1683,16 @@ func NewJSConsumerReplacementWithDifferentNameError(opts ...ErrorOption) *ApiErr
|
|||||||
return ApiErrors[JSConsumerReplacementWithDifferentNameErr]
|
return ApiErrors[JSConsumerReplacementWithDifferentNameErr]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewJSConsumerReplayPolicyInvalidError creates a new JSConsumerReplayPolicyInvalidErr error: "consumer replay policy invalid"
|
||||||
|
func NewJSConsumerReplayPolicyInvalidError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSConsumerReplayPolicyInvalidErr]
|
||||||
|
}
|
||||||
|
|
||||||
// NewJSConsumerReplicasExceedsStreamError creates a new JSConsumerReplicasExceedsStream error: "consumer config replica count exceeds parent stream"
|
// NewJSConsumerReplicasExceedsStreamError creates a new JSConsumerReplicasExceedsStream error: "consumer config replica count exceeds parent stream"
|
||||||
func NewJSConsumerReplicasExceedsStreamError(opts ...ErrorOption) *ApiError {
|
func NewJSConsumerReplicasExceedsStreamError(opts ...ErrorOption) *ApiError {
|
||||||
eopts := parseOpts(opts)
|
eopts := parseOpts(opts)
|
||||||
@@ -1597,6 +1845,106 @@ func NewJSMemoryResourcesExceededError(opts ...ErrorOption) *ApiError {
|
|||||||
return ApiErrors[JSMemoryResourcesExceededErr]
|
return ApiErrors[JSMemoryResourcesExceededErr]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewJSMessageCounterBrokenError creates a new JSMessageCounterBrokenErr error: "message counter is broken"
|
||||||
|
func NewJSMessageCounterBrokenError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSMessageCounterBrokenErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSMessageIncrDisabledError creates a new JSMessageIncrDisabledErr error: "message counters is disabled"
|
||||||
|
func NewJSMessageIncrDisabledError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSMessageIncrDisabledErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSMessageIncrInvalidError creates a new JSMessageIncrInvalidErr error: "message counter increment is invalid"
|
||||||
|
func NewJSMessageIncrInvalidError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSMessageIncrInvalidErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSMessageIncrMissingError creates a new JSMessageIncrMissingErr error: "message counter increment is missing"
|
||||||
|
func NewJSMessageIncrMissingError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSMessageIncrMissingErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSMessageIncrPayloadError creates a new JSMessageIncrPayloadErr error: "message counter has payload"
|
||||||
|
func NewJSMessageIncrPayloadError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSMessageIncrPayloadErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSMessageSchedulesDisabledError creates a new JSMessageSchedulesDisabledErr error: "message schedules is disabled"
|
||||||
|
func NewJSMessageSchedulesDisabledError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSMessageSchedulesDisabledErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSMessageSchedulesPatternInvalidError creates a new JSMessageSchedulesPatternInvalidErr error: "message schedules pattern is invalid"
|
||||||
|
func NewJSMessageSchedulesPatternInvalidError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSMessageSchedulesPatternInvalidErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSMessageSchedulesRollupInvalidError creates a new JSMessageSchedulesRollupInvalidErr error: "message schedules invalid rollup"
|
||||||
|
func NewJSMessageSchedulesRollupInvalidError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSMessageSchedulesRollupInvalidErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSMessageSchedulesTTLInvalidError creates a new JSMessageSchedulesTTLInvalidErr error: "message schedules invalid per-message TTL"
|
||||||
|
func NewJSMessageSchedulesTTLInvalidError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSMessageSchedulesTTLInvalidErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSMessageSchedulesTargetInvalidError creates a new JSMessageSchedulesTargetInvalidErr error: "message schedules target is invalid"
|
||||||
|
func NewJSMessageSchedulesTargetInvalidError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSMessageSchedulesTargetInvalidErr]
|
||||||
|
}
|
||||||
|
|
||||||
// NewJSMessageTTLDisabledError creates a new JSMessageTTLDisabledErr error: "per-message TTL is disabled"
|
// NewJSMessageTTLDisabledError creates a new JSMessageTTLDisabledErr error: "per-message TTL is disabled"
|
||||||
func NewJSMessageTTLDisabledError(opts ...ErrorOption) *ApiError {
|
func NewJSMessageTTLDisabledError(opts ...ErrorOption) *ApiError {
|
||||||
eopts := parseOpts(opts)
|
eopts := parseOpts(opts)
|
||||||
@@ -1705,6 +2053,26 @@ func NewJSMirrorOverlappingSubjectFiltersError(opts ...ErrorOption) *ApiError {
|
|||||||
return ApiErrors[JSMirrorOverlappingSubjectFilters]
|
return ApiErrors[JSMirrorOverlappingSubjectFilters]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewJSMirrorWithAtomicPublishError creates a new JSMirrorWithAtomicPublishErr error: "stream mirrors can not also use atomic publishing"
|
||||||
|
func NewJSMirrorWithAtomicPublishError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSMirrorWithAtomicPublishErr]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSMirrorWithCountersError creates a new JSMirrorWithCountersErr error: "stream mirrors can not also calculate counters"
|
||||||
|
func NewJSMirrorWithCountersError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSMirrorWithCountersErr]
|
||||||
|
}
|
||||||
|
|
||||||
// NewJSMirrorWithFirstSeqError creates a new JSMirrorWithFirstSeqErr error: "stream mirrors can not have first sequence configured"
|
// NewJSMirrorWithFirstSeqError creates a new JSMirrorWithFirstSeqErr error: "stream mirrors can not have first sequence configured"
|
||||||
func NewJSMirrorWithFirstSeqError(opts ...ErrorOption) *ApiError {
|
func NewJSMirrorWithFirstSeqError(opts ...ErrorOption) *ApiError {
|
||||||
eopts := parseOpts(opts)
|
eopts := parseOpts(opts)
|
||||||
@@ -1715,6 +2083,16 @@ func NewJSMirrorWithFirstSeqError(opts ...ErrorOption) *ApiError {
|
|||||||
return ApiErrors[JSMirrorWithFirstSeqErr]
|
return ApiErrors[JSMirrorWithFirstSeqErr]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewJSMirrorWithMsgSchedulesError creates a new JSMirrorWithMsgSchedulesErr error: "stream mirrors can not also schedule messages"
|
||||||
|
func NewJSMirrorWithMsgSchedulesError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSMirrorWithMsgSchedulesErr]
|
||||||
|
}
|
||||||
|
|
||||||
// NewJSMirrorWithSourcesError creates a new JSMirrorWithSourcesErr error: "stream mirrors can not also contain other sources"
|
// NewJSMirrorWithSourcesError creates a new JSMirrorWithSourcesErr error: "stream mirrors can not also contain other sources"
|
||||||
func NewJSMirrorWithSourcesError(opts ...ErrorOption) *ApiError {
|
func NewJSMirrorWithSourcesError(opts ...ErrorOption) *ApiError {
|
||||||
eopts := parseOpts(opts)
|
eopts := parseOpts(opts)
|
||||||
@@ -1867,6 +2245,16 @@ func NewJSReplicasCountCannotBeNegativeError(opts ...ErrorOption) *ApiError {
|
|||||||
return ApiErrors[JSReplicasCountCannotBeNegative]
|
return ApiErrors[JSReplicasCountCannotBeNegative]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewJSRequiredApiLevelError creates a new JSRequiredApiLevelErr error: "JetStream minimum api level required"
|
||||||
|
func NewJSRequiredApiLevelError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSRequiredApiLevelErr]
|
||||||
|
}
|
||||||
|
|
||||||
// NewJSRestoreSubscribeFailedError creates a new JSRestoreSubscribeFailedErrF error: "JetStream unable to subscribe to restore snapshot {subject}: {err}"
|
// NewJSRestoreSubscribeFailedError creates a new JSRestoreSubscribeFailedErrF error: "JetStream unable to subscribe to restore snapshot {subject}: {err}"
|
||||||
func NewJSRestoreSubscribeFailedError(err error, subject interface{}, opts ...ErrorOption) *ApiError {
|
func NewJSRestoreSubscribeFailedError(err error, subject interface{}, opts ...ErrorOption) *ApiError {
|
||||||
eopts := parseOpts(opts)
|
eopts := parseOpts(opts)
|
||||||
@@ -2007,6 +2395,16 @@ func NewJSSourceOverlappingSubjectFiltersError(opts ...ErrorOption) *ApiError {
|
|||||||
return ApiErrors[JSSourceOverlappingSubjectFilters]
|
return ApiErrors[JSSourceOverlappingSubjectFilters]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewJSSourceWithMsgSchedulesError creates a new JSSourceWithMsgSchedulesErr error: "stream source can not also schedule messages"
|
||||||
|
func NewJSSourceWithMsgSchedulesError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSSourceWithMsgSchedulesErr]
|
||||||
|
}
|
||||||
|
|
||||||
// NewJSStorageResourcesExceededError creates a new JSStorageResourcesExceededErr error: "insufficient storage resources available"
|
// NewJSStorageResourcesExceededError creates a new JSStorageResourcesExceededErr error: "insufficient storage resources available"
|
||||||
func NewJSStorageResourcesExceededError(opts ...ErrorOption) *ApiError {
|
func NewJSStorageResourcesExceededError(opts ...ErrorOption) *ApiError {
|
||||||
eopts := parseOpts(opts)
|
eopts := parseOpts(opts)
|
||||||
@@ -2075,6 +2473,16 @@ func NewJSStreamDuplicateMessageConflictError(opts ...ErrorOption) *ApiError {
|
|||||||
return ApiErrors[JSStreamDuplicateMessageConflict]
|
return ApiErrors[JSStreamDuplicateMessageConflict]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewJSStreamExpectedLastSeqPerSubjectInvalidError creates a new JSStreamExpectedLastSeqPerSubjectInvalid error: "missing sequence for expected last sequence per subject"
|
||||||
|
func NewJSStreamExpectedLastSeqPerSubjectInvalidError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSStreamExpectedLastSeqPerSubjectInvalid]
|
||||||
|
}
|
||||||
|
|
||||||
// NewJSStreamExpectedLastSeqPerSubjectNotReadyError creates a new JSStreamExpectedLastSeqPerSubjectNotReady error: "expected last sequence per subject temporarily unavailable"
|
// NewJSStreamExpectedLastSeqPerSubjectNotReadyError creates a new JSStreamExpectedLastSeqPerSubjectNotReady error: "expected last sequence per subject temporarily unavailable"
|
||||||
func NewJSStreamExpectedLastSeqPerSubjectNotReadyError(opts ...ErrorOption) *ApiError {
|
func NewJSStreamExpectedLastSeqPerSubjectNotReadyError(opts ...ErrorOption) *ApiError {
|
||||||
eopts := parseOpts(opts)
|
eopts := parseOpts(opts)
|
||||||
@@ -2241,6 +2649,16 @@ func NewJSStreamMessageExceedsMaximumError(opts ...ErrorOption) *ApiError {
|
|||||||
return ApiErrors[JSStreamMessageExceedsMaximumErr]
|
return ApiErrors[JSStreamMessageExceedsMaximumErr]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewJSStreamMinLastSeqError creates a new JSStreamMinLastSeqErr error: "min last sequence"
|
||||||
|
func NewJSStreamMinLastSeqError(opts ...ErrorOption) *ApiError {
|
||||||
|
eopts := parseOpts(opts)
|
||||||
|
if ae, ok := eopts.err.(*ApiError); ok {
|
||||||
|
return ae
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiErrors[JSStreamMinLastSeqErr]
|
||||||
|
}
|
||||||
|
|
||||||
// NewJSStreamMirrorNotUpdatableError creates a new JSStreamMirrorNotUpdatableErr error: "stream mirror configuration can not be updated"
|
// NewJSStreamMirrorNotUpdatableError creates a new JSStreamMirrorNotUpdatableErr error: "stream mirror configuration can not be updated"
|
||||||
func NewJSStreamMirrorNotUpdatableError(opts ...ErrorOption) *ApiError {
|
func NewJSStreamMirrorNotUpdatableError(opts ...ErrorOption) *ApiError {
|
||||||
eopts := parseOpts(opts)
|
eopts := parseOpts(opts)
|
||||||
|
|||||||
+22
-1
@@ -73,7 +73,7 @@ type JSStreamActionAdvisory struct {
|
|||||||
TypedEvent
|
TypedEvent
|
||||||
Stream string `json:"stream"`
|
Stream string `json:"stream"`
|
||||||
Action ActionAdvisoryType `json:"action"`
|
Action ActionAdvisoryType `json:"action"`
|
||||||
Template string `json:"template,omitempty"`
|
Template string `json:"template,omitempty"` // Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
Domain string `json:"domain,omitempty"`
|
Domain string `json:"domain,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,6 +253,27 @@ type JSStreamQuorumLostAdvisory struct {
|
|||||||
Domain string `json:"domain,omitempty"`
|
Domain string `json:"domain,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JSStreamBatchAbandonedAdvisoryType is sent when a stream's atomic batch is abandoned.
|
||||||
|
const JSStreamBatchAbandonedAdvisoryType = "io.nats.jetstream.advisory.v1.stream_batch_abandoned"
|
||||||
|
|
||||||
|
// JSStreamBatchAbandonedAdvisory indicates that a stream's batch was abandoned.
|
||||||
|
type JSStreamBatchAbandonedAdvisory struct {
|
||||||
|
TypedEvent
|
||||||
|
Account string `json:"account,omitempty"`
|
||||||
|
Stream string `json:"stream"`
|
||||||
|
Domain string `json:"domain,omitempty"`
|
||||||
|
BatchId string `json:"batch"`
|
||||||
|
Reason BatchAbandonReason `json:"reason"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BatchAbandonReason string
|
||||||
|
|
||||||
|
var (
|
||||||
|
BatchTimeout BatchAbandonReason = "timeout"
|
||||||
|
BatchLarge BatchAbandonReason = "large"
|
||||||
|
BatchIncomplete BatchAbandonReason = "incomplete"
|
||||||
|
)
|
||||||
|
|
||||||
// JSConsumerLeaderElectedAdvisoryType is sent when the system elects a leader for a consumer.
|
// JSConsumerLeaderElectedAdvisoryType is sent when the system elects a leader for a consumer.
|
||||||
const JSConsumerLeaderElectedAdvisoryType = "io.nats.jetstream.advisory.v1.consumer_leader_elected"
|
const JSConsumerLeaderElectedAdvisoryType = "io.nats.jetstream.advisory.v1.consumer_leader_elected"
|
||||||
|
|
||||||
|
|||||||
+31
-1
@@ -17,7 +17,7 @@ import "strconv"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// JSApiLevel is the maximum supported JetStream API level for this server.
|
// JSApiLevel is the maximum supported JetStream API level for this server.
|
||||||
JSApiLevel int = 1
|
JSApiLevel int = 2
|
||||||
|
|
||||||
JSRequiredLevelMetadataKey = "_nats.req.level"
|
JSRequiredLevelMetadataKey = "_nats.req.level"
|
||||||
JSServerVersionMetadataKey = "_nats.ver"
|
JSServerVersionMetadataKey = "_nats.ver"
|
||||||
@@ -62,6 +62,26 @@ func setStaticStreamMetadata(cfg *StreamConfig) {
|
|||||||
requires(1)
|
requires(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Counter CRDTs were added in v2.12 and require API level 2.
|
||||||
|
if cfg.AllowMsgCounter {
|
||||||
|
requires(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atomic batch publishing was added in v2.12 and require API level 2.
|
||||||
|
if cfg.AllowAtomicPublish {
|
||||||
|
requires(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message scheduling was added in v2.12 and require API level 2.
|
||||||
|
if cfg.AllowMsgSchedules {
|
||||||
|
requires(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async persist mode was added in v2.12 and requires API level 2.
|
||||||
|
if cfg.PersistMode == AsyncPersistMode {
|
||||||
|
requires(2)
|
||||||
|
}
|
||||||
|
|
||||||
cfg.Metadata[JSRequiredLevelMetadataKey] = strconv.Itoa(requiredApiLevel)
|
cfg.Metadata[JSRequiredLevelMetadataKey] = strconv.Itoa(requiredApiLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,3 +224,13 @@ func deleteDynamicMetadata(metadata map[string]string) {
|
|||||||
delete(metadata, JSServerVersionMetadataKey)
|
delete(metadata, JSServerVersionMetadataKey)
|
||||||
delete(metadata, JSServerLevelMetadataKey)
|
delete(metadata, JSServerLevelMetadataKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// errorOnRequiredApiLevel returns whether a request should be rejected based on the JSRequiredApiLevel header.
|
||||||
|
func errorOnRequiredApiLevel(hdr []byte) bool {
|
||||||
|
reqApiLevel := sliceHeader(JSRequiredApiLevel, hdr)
|
||||||
|
if len(reqApiLevel) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
minLevel, err := strconv.Atoi(string(reqApiLevel))
|
||||||
|
return err != nil || JSApiLevel < minLevel
|
||||||
|
}
|
||||||
|
|||||||
+50
-1
@@ -82,6 +82,8 @@ type leaf struct {
|
|||||||
remoteDomain string
|
remoteDomain string
|
||||||
// account name of remote server
|
// account name of remote server
|
||||||
remoteAccName string
|
remoteAccName string
|
||||||
|
// Whether or not we want to propagate east-west interest from other LNs.
|
||||||
|
isolated bool
|
||||||
// Used to suppress sub and unsub interest. Same as routes but our audience
|
// Used to suppress sub and unsub interest. Same as routes but our audience
|
||||||
// here is tied to this leaf node. This will hold all subscriptions except this
|
// here is tied to this leaf node. This will hold all subscriptions except this
|
||||||
// leaf nodes. This represents all the interest we want to send to the other side.
|
// leaf nodes. This represents all the interest we want to send to the other side.
|
||||||
@@ -130,6 +132,14 @@ func (c *client) isHubLeafNode() bool {
|
|||||||
return c.kind == LEAF && !c.leaf.isSpoke
|
return c.kind == LEAF && !c.leaf.isSpoke
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *client) isIsolatedLeafNode() bool {
|
||||||
|
// TODO(nat): In future we may want to pass in and consider an isolation
|
||||||
|
// group name here, which the hub and/or leaf could provide, so that we
|
||||||
|
// can isolate away certain LNs but not others on an opt-in basis. For
|
||||||
|
// now we will just isolate all LN interest until then.
|
||||||
|
return c.kind == LEAF && c.leaf.isolated
|
||||||
|
}
|
||||||
|
|
||||||
// This will spin up go routines to solicit the remote leaf node connections.
|
// This will spin up go routines to solicit the remote leaf node connections.
|
||||||
func (s *Server) solicitLeafNodeRemotes(remotes []*RemoteLeafOpts) {
|
func (s *Server) solicitLeafNodeRemotes(remotes []*RemoteLeafOpts) {
|
||||||
sysAccName := _EMPTY_
|
sysAccName := _EMPTY_
|
||||||
@@ -177,12 +187,20 @@ func (s *Server) solicitLeafNodeRemotes(remotes []*RemoteLeafOpts) {
|
|||||||
return remote
|
return remote
|
||||||
}
|
}
|
||||||
for _, r := range remotes {
|
for _, r := range remotes {
|
||||||
|
// We need to call this, even if the leaf is disabled. This is so that
|
||||||
|
// the number of internal configuration matches the options' remote leaf
|
||||||
|
// configuration required for configuration reload.
|
||||||
remote := addRemote(r, r.LocalAccount == sysAccName)
|
remote := addRemote(r, r.LocalAccount == sysAccName)
|
||||||
s.startGoRoutine(func() { s.connectToRemoteLeafNode(remote, true) })
|
if !r.Disabled {
|
||||||
|
s.startGoRoutine(func() { s.connectToRemoteLeafNode(remote, true) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) remoteLeafNodeStillValid(remote *leafNodeCfg) bool {
|
func (s *Server) remoteLeafNodeStillValid(remote *leafNodeCfg) bool {
|
||||||
|
if remote.Disabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
for _, ri := range s.getOpts().LeafNode.Remotes {
|
for _, ri := range s.getOpts().LeafNode.Remotes {
|
||||||
// FIXME(dlc) - What about auth changes?
|
// FIXME(dlc) - What about auth changes?
|
||||||
if reflect.DeepEqual(ri.URLs, remote.URLs) {
|
if reflect.DeepEqual(ri.URLs, remote.URLs) {
|
||||||
@@ -748,6 +766,7 @@ func (s *Server) startLeafNodeAcceptLoop() {
|
|||||||
Domain: opts.JetStreamDomain,
|
Domain: opts.JetStreamDomain,
|
||||||
Proto: s.getServerProto(),
|
Proto: s.getServerProto(),
|
||||||
InfoOnConnect: true,
|
InfoOnConnect: true,
|
||||||
|
JSApiLevel: JSApiLevel,
|
||||||
}
|
}
|
||||||
// If we have selected a random port...
|
// If we have selected a random port...
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
@@ -811,6 +830,7 @@ func (c *client) sendLeafConnect(clusterName string, headers bool) error {
|
|||||||
Compression: c.leaf.compression,
|
Compression: c.leaf.compression,
|
||||||
RemoteAccount: c.acc.GetName(),
|
RemoteAccount: c.acc.GetName(),
|
||||||
Proto: c.srv.getServerProto(),
|
Proto: c.srv.getServerProto(),
|
||||||
|
Isolate: c.leaf.remote.RequestIsolation,
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a signature callback is specified, this takes precedence over anything else.
|
// If a signature callback is specified, this takes precedence over anything else.
|
||||||
@@ -977,6 +997,13 @@ func (s *Server) createLeafNode(conn net.Conn, rURL *url.URL, remote *leafNodeCf
|
|||||||
// Do not update the smap here, we need to do it in initLeafNodeSmapAndSendSubs
|
// Do not update the smap here, we need to do it in initLeafNodeSmapAndSendSubs
|
||||||
c.leaf = &leaf{}
|
c.leaf = &leaf{}
|
||||||
|
|
||||||
|
// If the leafnode subject interest should be isolated, flag it here.
|
||||||
|
s.optsMu.RLock()
|
||||||
|
if c.leaf.isolated = s.opts.LeafNode.IsolateLeafnodeInterest; !c.leaf.isolated && remote != nil {
|
||||||
|
c.leaf.isolated = remote.LocalIsolation
|
||||||
|
}
|
||||||
|
s.optsMu.RUnlock()
|
||||||
|
|
||||||
// For accepted LN connections, ws will be != nil if it was accepted
|
// For accepted LN connections, ws will be != nil if it was accepted
|
||||||
// through the Websocket port.
|
// through the Websocket port.
|
||||||
c.ws = ws
|
c.ws = ws
|
||||||
@@ -1057,6 +1084,8 @@ func (s *Server) createLeafNode(conn net.Conn, rURL *url.URL, remote *leafNodeCf
|
|||||||
if cm := opts.LeafNode.Compression.Mode; cm != CompressionNotSupported {
|
if cm := opts.LeafNode.Compression.Mode; cm != CompressionNotSupported {
|
||||||
info.Compression = cm
|
info.Compression = cm
|
||||||
}
|
}
|
||||||
|
// We always send a nonce for LEAF connections. Do not change that without
|
||||||
|
// taking into account presence of proxy trusted keys.
|
||||||
s.generateNonce(nonce[:])
|
s.generateNonce(nonce[:])
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
}
|
}
|
||||||
@@ -1794,9 +1823,13 @@ func (s *Server) removeLeafNodeConnection(c *client) {
|
|||||||
c.leaf.gwSub = nil
|
c.leaf.gwSub = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
proxyKey := c.proxyKey
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
delete(s.leafs, cid)
|
delete(s.leafs, cid)
|
||||||
|
if proxyKey != _EMPTY_ {
|
||||||
|
s.removeProxiedConn(proxyKey, cid)
|
||||||
|
}
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
s.removeFromTempClients(cid)
|
s.removeFromTempClients(cid)
|
||||||
}
|
}
|
||||||
@@ -1818,6 +1851,7 @@ type leafConnectInfo struct {
|
|||||||
Headers bool `json:"headers,omitempty"`
|
Headers bool `json:"headers,omitempty"`
|
||||||
JetStream bool `json:"jetstream,omitempty"`
|
JetStream bool `json:"jetstream,omitempty"`
|
||||||
DenyPub []string `json:"deny_pub,omitempty"`
|
DenyPub []string `json:"deny_pub,omitempty"`
|
||||||
|
Isolate bool `json:"isolate,omitempty"`
|
||||||
|
|
||||||
// There was an existing field called:
|
// There was an existing field called:
|
||||||
// >> Comp bool `json:"compression,omitempty"`
|
// >> Comp bool `json:"compression,omitempty"`
|
||||||
@@ -1928,6 +1962,8 @@ func (c *client) processLeafNodeConnect(s *Server, arg []byte, lang string) erro
|
|||||||
c.leaf.remoteServer = proto.Name
|
c.leaf.remoteServer = proto.Name
|
||||||
// Remember the remote account name
|
// Remember the remote account name
|
||||||
c.leaf.remoteAccName = proto.RemoteAccount
|
c.leaf.remoteAccName = proto.RemoteAccount
|
||||||
|
// Remember if the leafnode requested isolation.
|
||||||
|
c.leaf.isolated = c.leaf.isolated || proto.Isolate
|
||||||
|
|
||||||
// If the other side has declared itself a hub, so we will take on the spoke role.
|
// If the other side has declared itself a hub, so we will take on the spoke role.
|
||||||
if proto.Hub {
|
if proto.Hub {
|
||||||
@@ -2044,12 +2080,16 @@ func (c *client) remoteCluster() string {
|
|||||||
// its permission settings for local enforcement.
|
// its permission settings for local enforcement.
|
||||||
func (s *Server) sendPermsAndAccountInfo(c *client) {
|
func (s *Server) sendPermsAndAccountInfo(c *client) {
|
||||||
// Copy
|
// Copy
|
||||||
|
s.mu.Lock()
|
||||||
info := s.copyLeafNodeInfo()
|
info := s.copyLeafNodeInfo()
|
||||||
|
s.mu.Unlock()
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
info.CID = c.cid
|
info.CID = c.cid
|
||||||
info.Import = c.opts.Import
|
info.Import = c.opts.Import
|
||||||
info.Export = c.opts.Export
|
info.Export = c.opts.Export
|
||||||
info.RemoteAccount = c.acc.Name
|
info.RemoteAccount = c.acc.Name
|
||||||
|
// s.SystemAccount() uses an atomic operation and does not get the server lock, so this is safe.
|
||||||
|
info.IsSystemAccount = c.acc == s.SystemAccount()
|
||||||
info.ConnectInfo = true
|
info.ConnectInfo = true
|
||||||
c.enqueueProto(generateInfoJSON(info))
|
c.enqueueProto(generateInfoJSON(info))
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@@ -2159,6 +2199,10 @@ func (s *Server) initLeafNodeSmapAndSendSubs(c *client) {
|
|||||||
c.Debugf("Not permitted to subscribe to %q on behalf of %s%s", sub.subject, accName, accNTag)
|
c.Debugf("Not permitted to subscribe to %q on behalf of %s%s", sub.subject, accName, accNTag)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Don't advertise interest from leafnodes to other isolated leafnodes.
|
||||||
|
if sub.client.kind == LEAF && c.isIsolatedLeafNode() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// We ignore ourselves here.
|
// We ignore ourselves here.
|
||||||
// Also don't add the subscription if it has a origin cluster and the
|
// Also don't add the subscription if it has a origin cluster and the
|
||||||
// cluster name matches the one of the client we are sending to.
|
// cluster name matches the one of the client we are sending to.
|
||||||
@@ -2280,6 +2324,11 @@ func (acc *Account) updateLeafNodesEx(sub *subscription, delta int32, hubOnly bo
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ln.mu.Lock()
|
ln.mu.Lock()
|
||||||
|
// Don't advertise interest from leafnodes to other isolated leafnodes.
|
||||||
|
if sub.client.kind == LEAF && ln.isIsolatedLeafNode() {
|
||||||
|
ln.mu.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
// If `hubOnly` is true, it means that we want to update only leafnodes
|
// If `hubOnly` is true, it means that we want to update only leafnodes
|
||||||
// that connect to this server (so isHubLeafNode() would return `true`).
|
// that connect to this server (so isHubLeafNode() would return `true`).
|
||||||
if hubOnly && !ln.isHubLeafNode() {
|
if hubOnly && !ln.isHubLeafNode() {
|
||||||
|
|||||||
+96
-10
@@ -24,7 +24,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nats-io/nats-server/v2/server/ats"
|
"github.com/nats-io/nats-server/v2/server/ats"
|
||||||
|
|
||||||
"github.com/nats-io/nats-server/v2/server/avl"
|
"github.com/nats-io/nats-server/v2/server/avl"
|
||||||
"github.com/nats-io/nats-server/v2/server/gsl"
|
"github.com/nats-io/nats-server/v2/server/gsl"
|
||||||
"github.com/nats-io/nats-server/v2/server/stree"
|
"github.com/nats-io/nats-server/v2/server/stree"
|
||||||
@@ -42,11 +41,12 @@ type memStore struct {
|
|||||||
maxp int64
|
maxp int64
|
||||||
scb StorageUpdateHandler
|
scb StorageUpdateHandler
|
||||||
rmcb StorageRemoveMsgHandler
|
rmcb StorageRemoveMsgHandler
|
||||||
sdmcb SubjectDeleteMarkerUpdateHandler
|
pmsgcb ProcessJetStreamMsgHandler
|
||||||
ageChk *time.Timer
|
ageChk *time.Timer
|
||||||
consumers int
|
consumers int
|
||||||
receivedAny bool
|
receivedAny bool
|
||||||
ttls *thw.HashWheel
|
ttls *thw.HashWheel
|
||||||
|
scheduling *MsgScheduling
|
||||||
sdm *SDMMeta
|
sdm *SDMMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +67,9 @@ func newMemStore(cfg *StreamConfig) (*memStore, error) {
|
|||||||
if cfg.AllowMsgTTL {
|
if cfg.AllowMsgTTL {
|
||||||
ms.ttls = thw.NewHashWheel()
|
ms.ttls = thw.NewHashWheel()
|
||||||
}
|
}
|
||||||
|
if cfg.AllowMsgSchedules {
|
||||||
|
ms.scheduling = newMsgScheduling(ms.runMsgScheduling)
|
||||||
|
}
|
||||||
if cfg.FirstSeq > 0 {
|
if cfg.FirstSeq > 0 {
|
||||||
if _, err := ms.purge(cfg.FirstSeq); err != nil {
|
if _, err := ms.purge(cfg.FirstSeq); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -95,6 +98,11 @@ func (ms *memStore) UpdateConfig(cfg *StreamConfig) error {
|
|||||||
} else if !cfg.AllowMsgTTL && ms.ttls != nil {
|
} else if !cfg.AllowMsgTTL && ms.ttls != nil {
|
||||||
ms.ttls = nil
|
ms.ttls = nil
|
||||||
}
|
}
|
||||||
|
if cfg.AllowMsgSchedules && ms.scheduling == nil {
|
||||||
|
ms.recoverMsgSchedulingState()
|
||||||
|
} else if !cfg.AllowMsgSchedules && ms.scheduling != nil {
|
||||||
|
ms.scheduling = nil
|
||||||
|
}
|
||||||
// Limits checks and enforcement.
|
// Limits checks and enforcement.
|
||||||
ms.enforceMsgLimit()
|
ms.enforceMsgLimit()
|
||||||
ms.enforceBytesLimit()
|
ms.enforceBytesLimit()
|
||||||
@@ -127,6 +135,9 @@ func (ms *memStore) UpdateConfig(cfg *StreamConfig) error {
|
|||||||
if cfg.MaxAge != 0 || cfg.AllowMsgTTL {
|
if cfg.MaxAge != 0 || cfg.AllowMsgTTL {
|
||||||
ms.expireMsgs()
|
ms.expireMsgs()
|
||||||
}
|
}
|
||||||
|
if cfg.AllowMsgSchedules {
|
||||||
|
ms.runMsgScheduling()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +165,29 @@ func (ms *memStore) recoverTTLState() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lock should be held.
|
||||||
|
func (ms *memStore) recoverMsgSchedulingState() {
|
||||||
|
ms.scheduling = newMsgScheduling(ms.runMsgScheduling)
|
||||||
|
if ms.state.Msgs == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
seq uint64
|
||||||
|
smv StoreMsg
|
||||||
|
sm *StoreMsg
|
||||||
|
)
|
||||||
|
defer ms.scheduling.resetTimer()
|
||||||
|
for sm, seq, _ = ms.loadNextMsgLocked(fwcs, true, 0, &smv); sm != nil; sm, seq, _ = ms.loadNextMsgLocked(fwcs, true, seq+1, &smv) {
|
||||||
|
if len(sm.hdr) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if schedule, ok := getMessageSchedule(sm.hdr); ok && !schedule.IsZero() {
|
||||||
|
ms.scheduling.init(seq, sm.subj, schedule.UnixNano())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stores a raw message with expected sequence number and timestamp.
|
// Stores a raw message with expected sequence number and timestamp.
|
||||||
// Lock should be held.
|
// Lock should be held.
|
||||||
func (ms *memStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts, ttl int64) error {
|
func (ms *memStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts, ttl int64) error {
|
||||||
@@ -271,6 +305,13 @@ func (ms *memStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts, tt
|
|||||||
ms.startAgeChk()
|
ms.startAgeChk()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Message scheduling.
|
||||||
|
if ms.scheduling != nil {
|
||||||
|
if schedule, ok := getMessageSchedule(hdr); ok && !schedule.IsZero() {
|
||||||
|
ms.scheduling.add(seq, subj, schedule.UnixNano())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,6 +401,11 @@ func (ms *memStore) SkipMsgs(seq uint64, num uint64) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FlushAllPending flushes all data that was still pending to be written.
|
||||||
|
func (ms *memStore) FlushAllPending() {
|
||||||
|
// Noop, in-memory store doesn't use async applying.
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterStorageUpdates registers a callback for updates to storage changes.
|
// RegisterStorageUpdates registers a callback for updates to storage changes.
|
||||||
// It will present number of messages and bytes as a signed integer and an
|
// It will present number of messages and bytes as a signed integer and an
|
||||||
// optional sequence number of the message if a single.
|
// optional sequence number of the message if a single.
|
||||||
@@ -377,10 +423,10 @@ func (ms *memStore) RegisterStorageRemoveMsg(cb StorageRemoveMsgHandler) {
|
|||||||
ms.mu.Unlock()
|
ms.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterSubjectDeleteMarkerUpdates registers a callback for updates to new subject delete markers.
|
// RegisterProcessJetStreamMsg registers a callback to process new JetStream messages.
|
||||||
func (ms *memStore) RegisterSubjectDeleteMarkerUpdates(cb SubjectDeleteMarkerUpdateHandler) {
|
func (ms *memStore) RegisterProcessJetStreamMsg(cb ProcessJetStreamMsgHandler) {
|
||||||
ms.mu.Lock()
|
ms.mu.Lock()
|
||||||
ms.sdmcb = cb
|
ms.pmsgcb = cb
|
||||||
ms.mu.Unlock()
|
ms.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1084,11 +1130,11 @@ func (ms *memStore) expireMsgs() {
|
|||||||
maxAge := int64(ms.cfg.MaxAge)
|
maxAge := int64(ms.cfg.MaxAge)
|
||||||
minAge := time.Now().UnixNano() - maxAge
|
minAge := time.Now().UnixNano() - maxAge
|
||||||
rmcb := ms.rmcb
|
rmcb := ms.rmcb
|
||||||
sdmcb := ms.sdmcb
|
pmsgcb := ms.pmsgcb
|
||||||
sdmTTL := int64(ms.cfg.SubjectDeleteMarkerTTL.Seconds())
|
sdmTTL := int64(ms.cfg.SubjectDeleteMarkerTTL.Seconds())
|
||||||
sdmEnabled := sdmTTL > 0
|
sdmEnabled := sdmTTL > 0
|
||||||
ms.mu.RUnlock()
|
ms.mu.RUnlock()
|
||||||
if sdmEnabled && (rmcb == nil || sdmcb == nil) {
|
if sdmEnabled && (rmcb == nil || pmsgcb == nil) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1244,12 +1290,39 @@ func (ms *memStore) handleRemovalOrSdm(seq uint64, subj string, sdm bool, sdmTTL
|
|||||||
subj: subj,
|
subj: subj,
|
||||||
hdr: hdr,
|
hdr: hdr,
|
||||||
}
|
}
|
||||||
ms.sdmcb(msg)
|
ms.pmsgcb(msg)
|
||||||
} else {
|
} else {
|
||||||
ms.rmcb(seq)
|
ms.rmcb(seq)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Will run through scheduled messages.
|
||||||
|
func (ms *memStore) runMsgScheduling() {
|
||||||
|
// TODO: Not great that we're holding the lock here, but the timed hash wheel and message scheduling isn't thread-safe.
|
||||||
|
ms.mu.Lock()
|
||||||
|
defer ms.mu.Unlock()
|
||||||
|
|
||||||
|
if ms.scheduling == nil || ms.pmsgcb == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduledMsgs := ms.scheduling.getScheduledMessages(func(seq uint64, smv *StoreMsg) *StoreMsg {
|
||||||
|
sm, _ := ms.loadMsgLocked(seq, smv, false)
|
||||||
|
return sm
|
||||||
|
})
|
||||||
|
if len(scheduledMsgs) > 0 {
|
||||||
|
ms.mu.Unlock()
|
||||||
|
for _, msg := range scheduledMsgs {
|
||||||
|
ms.pmsgcb(msg)
|
||||||
|
}
|
||||||
|
ms.mu.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ms.scheduling != nil {
|
||||||
|
ms.scheduling.resetTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PurgeEx will remove messages based on subject filters, sequence and number of messages to keep.
|
// PurgeEx will remove messages based on subject filters, sequence and number of messages to keep.
|
||||||
// Will return the number of purged messages.
|
// Will return the number of purged messages.
|
||||||
func (ms *memStore) PurgeEx(subject string, sequence, keep uint64) (purged uint64, err error) {
|
func (ms *memStore) PurgeEx(subject string, sequence, keep uint64) (purged uint64, err error) {
|
||||||
@@ -1952,12 +2025,25 @@ func (ms *memStore) Utilization() (total, reported uint64, err error) {
|
|||||||
return ms.state.Bytes, ms.state.Bytes, nil
|
return ms.state.Bytes, ms.state.Bytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func memStoreMsgSizeRaw(slen, hlen, mlen int) uint64 {
|
||||||
|
return uint64(slen + hlen + mlen + 16) // 8*2 for seq + age
|
||||||
|
}
|
||||||
|
|
||||||
func memStoreMsgSize(subj string, hdr, msg []byte) uint64 {
|
func memStoreMsgSize(subj string, hdr, msg []byte) uint64 {
|
||||||
return uint64(len(subj) + len(hdr) + len(msg) + 16) // 8*2 for seq + age
|
return memStoreMsgSizeRaw(len(subj), len(hdr), len(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetState resets any state that's temporary. For example when changing leaders.
|
||||||
|
func (ms *memStore) ResetState() {
|
||||||
|
ms.mu.Lock()
|
||||||
|
defer ms.mu.Unlock()
|
||||||
|
if ms.scheduling != nil {
|
||||||
|
ms.scheduling.clearInflight()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete is same as Stop for memory store.
|
// Delete is same as Stop for memory store.
|
||||||
func (ms *memStore) Delete() error {
|
func (ms *memStore) Delete(_ bool) error {
|
||||||
return ms.Stop()
|
return ms.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+106
-29
@@ -129,6 +129,7 @@ type ConnInfo struct {
|
|||||||
OutMsgs int64 `json:"out_msgs"`
|
OutMsgs int64 `json:"out_msgs"`
|
||||||
InBytes int64 `json:"in_bytes"`
|
InBytes int64 `json:"in_bytes"`
|
||||||
OutBytes int64 `json:"out_bytes"`
|
OutBytes int64 `json:"out_bytes"`
|
||||||
|
Stalls int64 `json:"stalls,omitempty"`
|
||||||
NumSubs uint32 `json:"subscriptions"`
|
NumSubs uint32 `json:"subscriptions"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Lang string `json:"lang,omitempty"`
|
Lang string `json:"lang,omitempty"`
|
||||||
@@ -146,11 +147,17 @@ type ConnInfo struct {
|
|||||||
NameTag string `json:"name_tag,omitempty"`
|
NameTag string `json:"name_tag,omitempty"`
|
||||||
Tags jwt.TagList `json:"tags,omitempty"`
|
Tags jwt.TagList `json:"tags,omitempty"`
|
||||||
MQTTClient string `json:"mqtt_client,omitempty"` // This is the MQTT client id
|
MQTTClient string `json:"mqtt_client,omitempty"` // This is the MQTT client id
|
||||||
|
Proxy *ProxyInfo `json:"proxy,omitempty"`
|
||||||
|
|
||||||
// Internal
|
// Internal
|
||||||
rtt int64 // For fast sorting
|
rtt int64 // For fast sorting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProxyInfo represents the information about this proxied connection.
|
||||||
|
type ProxyInfo struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
// TLSPeerCert contains basic information about a TLS peer certificate
|
// TLSPeerCert contains basic information about a TLS peer certificate
|
||||||
type TLSPeerCert struct {
|
type TLSPeerCert struct {
|
||||||
Subject string `json:"subject,omitempty"`
|
Subject string `json:"subject,omitempty"`
|
||||||
@@ -571,6 +578,8 @@ func (ci *ConnInfo) fill(client *client, nc net.Conn, now time.Time, auth bool)
|
|||||||
// we need to use atomic here.
|
// we need to use atomic here.
|
||||||
ci.InMsgs = atomic.LoadInt64(&client.inMsgs)
|
ci.InMsgs = atomic.LoadInt64(&client.inMsgs)
|
||||||
ci.InBytes = atomic.LoadInt64(&client.inBytes)
|
ci.InBytes = atomic.LoadInt64(&client.inBytes)
|
||||||
|
ci.Stalls = atomic.LoadInt64(&client.stalls)
|
||||||
|
ci.Proxy = createProxyInfo(client)
|
||||||
|
|
||||||
// If the connection is gone, too bad, we won't set TLSVersion and TLSCipher.
|
// If the connection is gone, too bad, we won't set TLSVersion and TLSCipher.
|
||||||
// Exclude clients that are still doing handshake so we don't block in
|
// Exclude clients that are still doing handshake so we don't block in
|
||||||
@@ -579,7 +588,7 @@ func (ci *ConnInfo) fill(client *client, nc net.Conn, now time.Time, auth bool)
|
|||||||
if conn, ok := nc.(*tls.Conn); ok {
|
if conn, ok := nc.(*tls.Conn); ok {
|
||||||
cs := conn.ConnectionState()
|
cs := conn.ConnectionState()
|
||||||
ci.TLSVersion = tlsVersion(cs.Version)
|
ci.TLSVersion = tlsVersion(cs.Version)
|
||||||
ci.TLSCipher = tlsCipher(cs.CipherSuite)
|
ci.TLSCipher = tls.CipherSuiteName(cs.CipherSuite)
|
||||||
if auth && len(cs.PeerCertificates) > 0 {
|
if auth && len(cs.PeerCertificates) > 0 {
|
||||||
ci.TLSPeerCerts = makePeerCerts(cs.PeerCertificates)
|
ci.TLSPeerCerts = makePeerCerts(cs.PeerCertificates)
|
||||||
}
|
}
|
||||||
@@ -593,6 +602,17 @@ func (ci *ConnInfo) fill(client *client, nc net.Conn, now time.Time, auth bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this client came from a trusted proxy, this will return a ProxyInfo
|
||||||
|
// to be used in ConnInfo or LeafInfo.
|
||||||
|
//
|
||||||
|
// Client lock must be held on entry.
|
||||||
|
func createProxyInfo(c *client) *ProxyInfo {
|
||||||
|
if c.proxyKey == _EMPTY_ {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &ProxyInfo{Key: c.proxyKey}
|
||||||
|
}
|
||||||
|
|
||||||
func makePeerCerts(pc []*x509.Certificate) []*TLSPeerCert {
|
func makePeerCerts(pc []*x509.Certificate) []*TLSPeerCert {
|
||||||
res := make([]*TLSPeerCert, len(pc))
|
res := make([]*TLSPeerCert, len(pc))
|
||||||
for i, c := range pc {
|
for i, c := range pc {
|
||||||
@@ -808,8 +828,10 @@ type RouteInfo struct {
|
|||||||
|
|
||||||
// Routez returns a Routez struct containing information about routes.
|
// Routez returns a Routez struct containing information about routes.
|
||||||
func (s *Server) Routez(routezOpts *RoutezOptions) (*Routez, error) {
|
func (s *Server) Routez(routezOpts *RoutezOptions) (*Routez, error) {
|
||||||
rs := &Routez{Routes: []*RouteInfo{}}
|
rs := &Routez{
|
||||||
rs.Now = time.Now().UTC()
|
Now: time.Now().UTC(),
|
||||||
|
Routes: []*RouteInfo{},
|
||||||
|
}
|
||||||
|
|
||||||
if routezOpts == nil {
|
if routezOpts == nil {
|
||||||
routezOpts = &RoutezOptions{}
|
routezOpts = &RoutezOptions{}
|
||||||
@@ -826,7 +848,7 @@ func (s *Server) Routez(routezOpts *RoutezOptions) (*Routez, error) {
|
|||||||
rs.Import = perms.Import
|
rs.Import = perms.Import
|
||||||
rs.Export = perms.Export
|
rs.Export = perms.Export
|
||||||
}
|
}
|
||||||
rs.Name = s.getOpts().ServerName
|
rs.Name = s.info.Name
|
||||||
|
|
||||||
addRoute := func(r *client) {
|
addRoute := func(r *client) {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
@@ -1003,7 +1025,15 @@ func (s *Server) Subsz(opts *SubszOptions) (*Subsz, error) {
|
|||||||
slStats := &SublistStats{}
|
slStats := &SublistStats{}
|
||||||
|
|
||||||
// FIXME(dlc) - Make account aware.
|
// FIXME(dlc) - Make account aware.
|
||||||
sz := &Subsz{s.info.ID, time.Now().UTC(), slStats, 0, offset, limit, nil}
|
sz := &Subsz{
|
||||||
|
ID: s.info.ID,
|
||||||
|
Now: time.Now().UTC(),
|
||||||
|
SublistStats: slStats,
|
||||||
|
Total: 0,
|
||||||
|
Offset: offset,
|
||||||
|
Limit: limit,
|
||||||
|
Subs: nil,
|
||||||
|
}
|
||||||
|
|
||||||
if subdetail {
|
if subdetail {
|
||||||
var raw [4096]*subscription
|
var raw [4096]*subscription
|
||||||
@@ -1100,12 +1130,7 @@ func (s *Server) HandleSubsz(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var b []byte
|
var b []byte
|
||||||
|
b, err = json.MarshalIndent(st, "", " ")
|
||||||
if len(st.Subs) == 0 {
|
|
||||||
b, err = json.MarshalIndent(st.SublistStats, "", " ")
|
|
||||||
} else {
|
|
||||||
b, err = json.MarshalIndent(st, "", " ")
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Errorf("Error marshaling response to /subscriptionsz request: %v", err)
|
s.Errorf("Error marshaling response to /subscriptionsz request: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1237,6 +1262,8 @@ type Varz struct {
|
|||||||
InBytes int64 `json:"in_bytes"` // InBytes is the number of bytes this server received
|
InBytes int64 `json:"in_bytes"` // InBytes is the number of bytes this server received
|
||||||
OutBytes int64 `json:"out_bytes"` // OutMsgs is the number of bytes this server sent
|
OutBytes int64 `json:"out_bytes"` // OutMsgs is the number of bytes this server sent
|
||||||
SlowConsumers int64 `json:"slow_consumers"` // SlowConsumers is the total count of clients that were disconnected since start due to being slow consumers
|
SlowConsumers int64 `json:"slow_consumers"` // SlowConsumers is the total count of clients that were disconnected since start due to being slow consumers
|
||||||
|
StaleConnections int64 `json:"stale_connections"` // StaleConnections is the total count of stale connections that were detected
|
||||||
|
StalledClients int64 `json:"stalled_clients"` // StalledClients is the total number of times that clients have been stalled.
|
||||||
Subscriptions uint32 `json:"subscriptions"` // Subscriptions is the count of active subscriptions
|
Subscriptions uint32 `json:"subscriptions"` // Subscriptions is the count of active subscriptions
|
||||||
HTTPReqStats map[string]uint64 `json:"http_req_stats"` // HTTPReqStats is the number of requests each HTTP endpoint received
|
HTTPReqStats map[string]uint64 `json:"http_req_stats"` // HTTPReqStats is the number of requests each HTTP endpoint received
|
||||||
ConfigLoadTime time.Time `json:"config_load_time"` // ConfigLoadTime is the time the configuration was loaded or reloaded
|
ConfigLoadTime time.Time `json:"config_load_time"` // ConfigLoadTime is the time the configuration was loaded or reloaded
|
||||||
@@ -1247,8 +1274,10 @@ type Varz struct {
|
|||||||
TrustedOperatorsClaim []*jwt.OperatorClaims `json:"trusted_operators_claim,omitempty"` // TrustedOperatorsClaim is the decoded claims for each trusted operator
|
TrustedOperatorsClaim []*jwt.OperatorClaims `json:"trusted_operators_claim,omitempty"` // TrustedOperatorsClaim is the decoded claims for each trusted operator
|
||||||
SystemAccount string `json:"system_account,omitempty"` // SystemAccount is the name of the System account
|
SystemAccount string `json:"system_account,omitempty"` // SystemAccount is the name of the System account
|
||||||
PinnedAccountFail uint64 `json:"pinned_account_fails,omitempty"` // PinnedAccountFail is how often user logon fails due to the issuer account not being pinned.
|
PinnedAccountFail uint64 `json:"pinned_account_fails,omitempty"` // PinnedAccountFail is how often user logon fails due to the issuer account not being pinned.
|
||||||
OCSPResponseCache *OCSPResponseCacheVarz `json:"ocsp_peer_cache,omitempty"` // OCSPResponseCache is the state of the OCSP cache // OCSPResponseCache holds information about
|
OCSPResponseCache *OCSPResponseCacheVarz `json:"ocsp_peer_cache,omitempty"` // OCSPResponseCache is the state of the OCSP cache
|
||||||
SlowConsumersStats *SlowConsumersStats `json:"slow_consumer_stats"` // SlowConsumersStats is statistics about all detected Slow Consumer
|
SlowConsumersStats *SlowConsumersStats `json:"slow_consumer_stats"` // SlowConsumersStats are statistics about all detected Slow Consumer
|
||||||
|
StaleConnectionStats *StaleConnectionStats `json:"stale_connection_stats,omitempty"` // StaleConnectionStats are statistics about all detected Stale Connections
|
||||||
|
Proxies *ProxiesOptsVarz `json:"proxies,omitempty"` // Proxies hold information about network proxy devices
|
||||||
}
|
}
|
||||||
|
|
||||||
// JetStreamVarz contains basic runtime information about jetstream
|
// JetStreamVarz contains basic runtime information about jetstream
|
||||||
@@ -1365,6 +1394,16 @@ type OCSPResponseCacheVarz struct {
|
|||||||
Unknowns int64 `json:"cached_unknown_responses,omitempty"` // Unknowns is how many of the stored cache entries are unknown responses
|
Unknowns int64 `json:"cached_unknown_responses,omitempty"` // Unknowns is how many of the stored cache entries are unknown responses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProxiesOptsVarz contains proxies information
|
||||||
|
type ProxiesOptsVarz struct {
|
||||||
|
Trusted []*ProxyOptsVarz `json:"trusted,omitempty"` // Trusted holds a list of trusted proxies
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyOptsVarz contains proxy information
|
||||||
|
type ProxyOptsVarz struct {
|
||||||
|
Key string `json:"key"` // Key is the public key of the proxy
|
||||||
|
}
|
||||||
|
|
||||||
// VarzOptions are the options passed to Varz().
|
// VarzOptions are the options passed to Varz().
|
||||||
// Currently, there are no options defined.
|
// Currently, there are no options defined.
|
||||||
type VarzOptions struct{}
|
type VarzOptions struct{}
|
||||||
@@ -1377,6 +1416,14 @@ type SlowConsumersStats struct {
|
|||||||
Leafs uint64 `json:"leafs"` // Leafs is how many Leafnodes were slow consumers
|
Leafs uint64 `json:"leafs"` // Leafs is how many Leafnodes were slow consumers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StaleConnectionStats contains information about the stale connections from different type of connections.
|
||||||
|
type StaleConnectionStats struct {
|
||||||
|
Clients uint64 `json:"clients"` // Clients is how many Client connections became stale connections
|
||||||
|
Routes uint64 `json:"routes"` // Routes is how many Route connections became stale connections
|
||||||
|
Gateways uint64 `json:"gateways"` // Gateways is how many Gateway connections became stale connections
|
||||||
|
Leafs uint64 `json:"leafs"` // Leafs is how many Leafnode connections became stale connections
|
||||||
|
}
|
||||||
|
|
||||||
func myUptime(d time.Duration) string {
|
func myUptime(d time.Duration) string {
|
||||||
// Just use total seconds for uptime, and display days / years
|
// Just use total seconds for uptime, and display days / years
|
||||||
tsecs := d / time.Second
|
tsecs := d / time.Second
|
||||||
@@ -1615,7 +1662,6 @@ func (s *Server) createVarz(pcpu float64, rss int64) *Varz {
|
|||||||
MaxSubs: opts.MaxSubs,
|
MaxSubs: opts.MaxSubs,
|
||||||
Cores: runtime.NumCPU(),
|
Cores: runtime.NumCPU(),
|
||||||
MaxProcs: runtime.GOMAXPROCS(0),
|
MaxProcs: runtime.GOMAXPROCS(0),
|
||||||
Tags: opts.Tags,
|
|
||||||
TrustedOperatorsJwt: opts.operatorJWT,
|
TrustedOperatorsJwt: opts.operatorJWT,
|
||||||
TrustedOperatorsClaim: opts.TrustedOperators,
|
TrustedOperatorsClaim: opts.TrustedOperators,
|
||||||
}
|
}
|
||||||
@@ -1702,6 +1748,8 @@ func (s *Server) updateVarzConfigReloadableFields(v *Varz) {
|
|||||||
v.WriteDeadline = opts.WriteDeadline
|
v.WriteDeadline = opts.WriteDeadline
|
||||||
v.ConfigLoadTime = s.configTime.UTC()
|
v.ConfigLoadTime = s.configTime.UTC()
|
||||||
v.ConfigDigest = opts.configDigest
|
v.ConfigDigest = opts.configDigest
|
||||||
|
v.Tags = opts.Tags
|
||||||
|
v.Metadata = opts.Metadata
|
||||||
// Update route URLs if applicable
|
// Update route URLs if applicable
|
||||||
if s.varzUpdateRouteURLs {
|
if s.varzUpdateRouteURLs {
|
||||||
v.Cluster.URLs = urlsToStrings(opts.Routes)
|
v.Cluster.URLs = urlsToStrings(opts.Routes)
|
||||||
@@ -1714,6 +1762,19 @@ func (s *Server) updateVarzConfigReloadableFields(v *Varz) {
|
|||||||
v.Websocket.TLSPinnedCerts = getPinnedCertsAsSlice(opts.Websocket.TLSPinnedCerts)
|
v.Websocket.TLSPinnedCerts = getPinnedCertsAsSlice(opts.Websocket.TLSPinnedCerts)
|
||||||
|
|
||||||
v.TLSOCSPPeerVerify = s.ocspPeerVerify && v.TLSRequired && s.opts.tlsConfigOpts != nil && s.opts.tlsConfigOpts.OCSPPeerConfig != nil && s.opts.tlsConfigOpts.OCSPPeerConfig.Verify
|
v.TLSOCSPPeerVerify = s.ocspPeerVerify && v.TLSRequired && s.opts.tlsConfigOpts != nil && s.opts.tlsConfigOpts.OCSPPeerConfig != nil && s.opts.tlsConfigOpts.OCSPPeerConfig.Verify
|
||||||
|
|
||||||
|
if opts.Proxies != nil {
|
||||||
|
if v.Proxies == nil {
|
||||||
|
v.Proxies = &ProxiesOptsVarz{}
|
||||||
|
}
|
||||||
|
trusted := make([]*ProxyOptsVarz, 0, len(opts.Proxies.Trusted))
|
||||||
|
for _, t := range opts.Proxies.Trusted {
|
||||||
|
trusted = append(trusted, &ProxyOptsVarz{Key: t.Key})
|
||||||
|
}
|
||||||
|
v.Proxies.Trusted = trusted
|
||||||
|
} else {
|
||||||
|
v.Proxies = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPinnedCertsAsSlice(certs PinnedCertSet) []string {
|
func getPinnedCertsAsSlice(certs PinnedCertSet) []string {
|
||||||
@@ -1752,12 +1813,20 @@ func (s *Server) updateVarzRuntimeFields(v *Varz, forceUpdate bool, pcpu float64
|
|||||||
v.OutMsgs = atomic.LoadInt64(&s.outMsgs)
|
v.OutMsgs = atomic.LoadInt64(&s.outMsgs)
|
||||||
v.OutBytes = atomic.LoadInt64(&s.outBytes)
|
v.OutBytes = atomic.LoadInt64(&s.outBytes)
|
||||||
v.SlowConsumers = atomic.LoadInt64(&s.slowConsumers)
|
v.SlowConsumers = atomic.LoadInt64(&s.slowConsumers)
|
||||||
|
v.StalledClients = atomic.LoadInt64(&s.stalls)
|
||||||
v.SlowConsumersStats = &SlowConsumersStats{
|
v.SlowConsumersStats = &SlowConsumersStats{
|
||||||
Clients: s.NumSlowConsumersClients(),
|
Clients: s.NumSlowConsumersClients(),
|
||||||
Routes: s.NumSlowConsumersRoutes(),
|
Routes: s.NumSlowConsumersRoutes(),
|
||||||
Gateways: s.NumSlowConsumersGateways(),
|
Gateways: s.NumSlowConsumersGateways(),
|
||||||
Leafs: s.NumSlowConsumersLeafs(),
|
Leafs: s.NumSlowConsumersLeafs(),
|
||||||
}
|
}
|
||||||
|
v.StaleConnections = atomic.LoadInt64(&s.staleConnections)
|
||||||
|
v.StaleConnectionStats = &StaleConnectionStats{
|
||||||
|
Clients: s.NumStaleConnectionsClients(),
|
||||||
|
Routes: s.NumStaleConnectionsRoutes(),
|
||||||
|
Gateways: s.NumStaleConnectionsGateways(),
|
||||||
|
Leafs: s.NumStaleConnectionsLeafs(),
|
||||||
|
}
|
||||||
v.PinnedAccountFail = atomic.LoadUint64(&s.pinnedAccFail)
|
v.PinnedAccountFail = atomic.LoadUint64(&s.pinnedAccFail)
|
||||||
|
|
||||||
// Make sure to reset in case we are re-using.
|
// Make sure to reset in case we are re-using.
|
||||||
@@ -2248,20 +2317,22 @@ type LeafzOptions struct {
|
|||||||
|
|
||||||
// LeafInfo has detailed information on each remote leafnode connection.
|
// LeafInfo has detailed information on each remote leafnode connection.
|
||||||
type LeafInfo struct {
|
type LeafInfo struct {
|
||||||
ID uint64 `json:"id"`
|
ID uint64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
IsSpoke bool `json:"is_spoke"`
|
IsSpoke bool `json:"is_spoke"`
|
||||||
Account string `json:"account"`
|
IsIsolated bool `json:"is_isolated,omitempty"`
|
||||||
IP string `json:"ip"`
|
Account string `json:"account"`
|
||||||
Port int `json:"port"`
|
IP string `json:"ip"`
|
||||||
RTT string `json:"rtt,omitempty"`
|
Port int `json:"port"`
|
||||||
InMsgs int64 `json:"in_msgs"`
|
RTT string `json:"rtt,omitempty"`
|
||||||
OutMsgs int64 `json:"out_msgs"`
|
InMsgs int64 `json:"in_msgs"`
|
||||||
InBytes int64 `json:"in_bytes"`
|
OutMsgs int64 `json:"out_msgs"`
|
||||||
OutBytes int64 `json:"out_bytes"`
|
InBytes int64 `json:"in_bytes"`
|
||||||
NumSubs uint32 `json:"subscriptions"`
|
OutBytes int64 `json:"out_bytes"`
|
||||||
Subs []string `json:"subscriptions_list,omitempty"`
|
NumSubs uint32 `json:"subscriptions"`
|
||||||
Compression string `json:"compression,omitempty"`
|
Subs []string `json:"subscriptions_list,omitempty"`
|
||||||
|
Compression string `json:"compression,omitempty"`
|
||||||
|
Proxy *ProxyInfo `json:"proxy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Leafz returns a Leafz structure containing information about leafnodes.
|
// Leafz returns a Leafz structure containing information about leafnodes.
|
||||||
@@ -2294,6 +2365,7 @@ func (s *Server) Leafz(opts *LeafzOptions) (*Leafz, error) {
|
|||||||
ID: ln.cid,
|
ID: ln.cid,
|
||||||
Name: ln.leaf.remoteServer,
|
Name: ln.leaf.remoteServer,
|
||||||
IsSpoke: ln.isSpokeLeafNode(),
|
IsSpoke: ln.isSpokeLeafNode(),
|
||||||
|
IsIsolated: ln.leaf.isolated,
|
||||||
Account: ln.acc.Name,
|
Account: ln.acc.Name,
|
||||||
IP: ln.host,
|
IP: ln.host,
|
||||||
Port: int(ln.port),
|
Port: int(ln.port),
|
||||||
@@ -2304,6 +2376,7 @@ func (s *Server) Leafz(opts *LeafzOptions) (*Leafz, error) {
|
|||||||
OutBytes: ln.outBytes,
|
OutBytes: ln.outBytes,
|
||||||
NumSubs: uint32(len(ln.subs)),
|
NumSubs: uint32(len(ln.subs)),
|
||||||
Compression: ln.leaf.compression,
|
Compression: ln.leaf.compression,
|
||||||
|
Proxy: createProxyInfo(ln),
|
||||||
}
|
}
|
||||||
if opts != nil && opts.Subscriptions {
|
if opts != nil && opts.Subscriptions {
|
||||||
lni.Subs = make([]string, 0, len(ln.subs))
|
lni.Subs = make([]string, 0, len(ln.subs))
|
||||||
@@ -2373,7 +2446,7 @@ func (s *Server) AccountStatz(opts *AccountStatzOptions) (*AccountStatz, error)
|
|||||||
s.accounts.Range(func(key, a any) bool {
|
s.accounts.Range(func(key, a any) bool {
|
||||||
acc := a.(*Account)
|
acc := a.(*Account)
|
||||||
acc.mu.RLock()
|
acc.mu.RLock()
|
||||||
if opts.IncludeUnused || acc.numLocalConnections() != 0 {
|
if (opts != nil && opts.IncludeUnused) || acc.numLocalConnections() != 0 {
|
||||||
stz.Accounts = append(stz.Accounts, acc.statz())
|
stz.Accounts = append(stz.Accounts, acc.statz())
|
||||||
}
|
}
|
||||||
acc.mu.RUnlock()
|
acc.mu.RUnlock()
|
||||||
@@ -2516,6 +2589,10 @@ func (reason ClosedState) String() string {
|
|||||||
return "Cluster Names Identical"
|
return "Cluster Names Identical"
|
||||||
case Kicked:
|
case Kicked:
|
||||||
return "Kicked"
|
return "Kicked"
|
||||||
|
case ProxyNotTrusted:
|
||||||
|
return "Proxy Not Trusted"
|
||||||
|
case ProxyRequired:
|
||||||
|
return "Proxy Required"
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Unknown State"
|
return "Unknown State"
|
||||||
|
|||||||
+2
-2
@@ -514,7 +514,7 @@ func (s *Server) startMQTT() {
|
|||||||
hp := net.JoinHostPort(o.Host, strconv.Itoa(port))
|
hp := net.JoinHostPort(o.Host, strconv.Itoa(port))
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.mqtt.sessmgr.sessions = make(map[string]*mqttAccountSessionManager)
|
s.mqtt.sessmgr.sessions = make(map[string]*mqttAccountSessionManager)
|
||||||
hl, err = net.Listen("tcp", hp)
|
hl, err = natsListen("tcp", hp)
|
||||||
s.mqtt.listenerErr = err
|
s.mqtt.listenerErr = err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
@@ -647,7 +647,7 @@ func (s *Server) createMQTTClient(conn net.Conn, ws *websocket) *client {
|
|||||||
if tlsRequired {
|
if tlsRequired {
|
||||||
c.Debugf("TLS handshake complete")
|
c.Debugf("TLS handshake complete")
|
||||||
cs := c.nc.(*tls.Conn).ConnectionState()
|
cs := c.nc.(*tls.Conn).ConnectionState()
|
||||||
c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tlsCipher(cs.CipherSuite))
|
c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tls.CipherSuiteName(cs.CipherSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ func (s *Server) NonceRequired() bool {
|
|||||||
// nonceRequired tells us if we should send a nonce.
|
// nonceRequired tells us if we should send a nonce.
|
||||||
// Lock should be held on entry.
|
// Lock should be held on entry.
|
||||||
func (s *Server) nonceRequired() bool {
|
func (s *Server) nonceRequired() bool {
|
||||||
return s.getOpts().AlwaysEnableNonce || len(s.nkeys) > 0 || s.trustedKeys != nil
|
return s.getOpts().AlwaysEnableNonce || len(s.nkeys) > 0 || s.trustedKeys != nil || len(s.proxiesKeyPairs) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a nonce for INFO challenge.
|
// Generate a nonce for INFO challenge.
|
||||||
|
|||||||
+265
-23
@@ -77,6 +77,7 @@ type ClusterOpts struct {
|
|||||||
Advertise string `json:"-"`
|
Advertise string `json:"-"`
|
||||||
NoAdvertise bool `json:"-"`
|
NoAdvertise bool `json:"-"`
|
||||||
ConnectRetries int `json:"-"`
|
ConnectRetries int `json:"-"`
|
||||||
|
ConnectBackoff bool `json:"-"`
|
||||||
PoolSize int `json:"-"`
|
PoolSize int `json:"-"`
|
||||||
PinnedAccounts []string `json:"-"`
|
PinnedAccounts []string `json:"-"`
|
||||||
Compression CompressionOpts `json:"-"`
|
Compression CompressionOpts `json:"-"`
|
||||||
@@ -121,6 +122,7 @@ type GatewayOpts struct {
|
|||||||
TLSPinnedCerts PinnedCertSet `json:"-"`
|
TLSPinnedCerts PinnedCertSet `json:"-"`
|
||||||
Advertise string `json:"advertise,omitempty"`
|
Advertise string `json:"advertise,omitempty"`
|
||||||
ConnectRetries int `json:"connect_retries,omitempty"`
|
ConnectRetries int `json:"connect_retries,omitempty"`
|
||||||
|
ConnectBackoff bool `json:"connect_backoff,omitempty"`
|
||||||
Gateways []*RemoteGatewayOpts `json:"gateways,omitempty"`
|
Gateways []*RemoteGatewayOpts `json:"gateways,omitempty"`
|
||||||
RejectUnknown bool `json:"reject_unknown,omitempty"` // config got renamed to reject_unknown_cluster
|
RejectUnknown bool `json:"reject_unknown,omitempty"` // config got renamed to reject_unknown_cluster
|
||||||
|
|
||||||
@@ -149,6 +151,7 @@ type LeafNodeOpts struct {
|
|||||||
Port int `json:"port,omitempty"`
|
Port int `json:"port,omitempty"`
|
||||||
Username string `json:"-"`
|
Username string `json:"-"`
|
||||||
Password string `json:"-"`
|
Password string `json:"-"`
|
||||||
|
ProxyRequired bool `json:"-"`
|
||||||
Nkey string `json:"-"`
|
Nkey string `json:"-"`
|
||||||
Account string `json:"-"`
|
Account string `json:"-"`
|
||||||
Users []*User `json:"-"`
|
Users []*User `json:"-"`
|
||||||
@@ -186,6 +189,10 @@ type LeafNodeOpts struct {
|
|||||||
// least" test).
|
// least" test).
|
||||||
MinVersion string
|
MinVersion string
|
||||||
|
|
||||||
|
// Isolate subject interest from other leafnode connections, preventing
|
||||||
|
// east-west propagation.
|
||||||
|
IsolateLeafnodeInterest bool `json:"-"`
|
||||||
|
|
||||||
// Not exported, for tests.
|
// Not exported, for tests.
|
||||||
resolver netResolver
|
resolver netResolver
|
||||||
dialTimeout time.Duration
|
dialTimeout time.Duration
|
||||||
@@ -245,14 +252,30 @@ type RemoteLeafOpts struct {
|
|||||||
// If JetStreamClusterMigrate is set to true, this is the time after which the leader
|
// If JetStreamClusterMigrate is set to true, this is the time after which the leader
|
||||||
// will be migrated away from this server if still disconnected.
|
// will be migrated away from this server if still disconnected.
|
||||||
JetStreamClusterMigrateDelay time.Duration `json:"jetstream_cluster_migrate_delay,omitempty"`
|
JetStreamClusterMigrateDelay time.Duration `json:"jetstream_cluster_migrate_delay,omitempty"`
|
||||||
|
|
||||||
|
// LocalIsolation isolates this remote from east-west subject interest originating locally.
|
||||||
|
LocalIsolation bool `json:"local_isolation,omitempty"`
|
||||||
|
|
||||||
|
// RequestIsolation asks the remote side to isolate us from their east-west subject interest.
|
||||||
|
RequestIsolation bool `json:"request_isolation,omitempty"`
|
||||||
|
|
||||||
|
// If this is set to true, the connection to this remote will not be solicited.
|
||||||
|
// During a configuration reload, if this is changed from `false` to `true`, the
|
||||||
|
// existing connection will be closed and not solicited again (until it is changed
|
||||||
|
// to `false` again.
|
||||||
|
Disabled bool `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSLimitOpts are active limits for the meta cluster
|
// JSLimitOpts are active limits for the meta cluster
|
||||||
type JSLimitOpts struct {
|
type JSLimitOpts struct {
|
||||||
MaxRequestBatch int `json:"max_request_batch,omitempty"` // MaxRequestBatch is the maximum amount of updates that can be sent in a batch
|
MaxRequestBatch int `json:"max_request_batch,omitempty"` // MaxRequestBatch is the maximum amount of updates that can be sent in a batch
|
||||||
MaxAckPending int `json:"max_ack_pending,omitempty"` // MaxAckPending is the server limit for maximum amount of outstanding Acks
|
MaxAckPending int `json:"max_ack_pending,omitempty"` // MaxAckPending is the server limit for maximum amount of outstanding Acks
|
||||||
MaxHAAssets int `json:"max_ha_assets,omitempty"` // MaxHAAssets is the maximum of Streams and Consumers that may have more than 1 replica
|
MaxHAAssets int `json:"max_ha_assets,omitempty"` // MaxHAAssets is the maximum of Streams and Consumers that may have more than 1 replica
|
||||||
Duplicates time.Duration `json:"max_duplicate_window,omitempty"` // Duplicates is the maximum value for duplicate tracking on Streams
|
Duplicates time.Duration `json:"max_duplicate_window,omitempty"` // Duplicates is the maximum value for duplicate tracking on Streams
|
||||||
|
MaxBatchInflightPerStream int `json:"max_batch_inflight_per_stream,omitempty"` // MaxBatchInflightPerStream is the maximum amount of open batches per stream
|
||||||
|
MaxBatchInflightTotal int `json:"max_batch_inflight_total,omitempty"` // MaxBatchInflightTotal is the maximum amount of total open batches per server
|
||||||
|
MaxBatchSize int `json:"max_batch_size,omitempty"` // MaxBatchSize is the maximum amount of messages allowed in a batch publish to a Stream
|
||||||
|
MaxBatchTimeout time.Duration `json:"max_batch_timeout,omitempty"` // MaxBatchTimeout is the maximum time to receive the commit message after receiving the first message of a batch
|
||||||
}
|
}
|
||||||
|
|
||||||
type JSTpmOpts struct {
|
type JSTpmOpts struct {
|
||||||
@@ -291,7 +314,8 @@ type Options struct {
|
|||||||
Trace bool `json:"-"`
|
Trace bool `json:"-"`
|
||||||
Debug bool `json:"-"`
|
Debug bool `json:"-"`
|
||||||
TraceVerbose bool `json:"-"`
|
TraceVerbose bool `json:"-"`
|
||||||
// TraceHeaders if true will only trace message headers, not the payload
|
|
||||||
|
// TraceHeaders if true will only trace message headers, not the payload.
|
||||||
TraceHeaders bool `json:"-"`
|
TraceHeaders bool `json:"-"`
|
||||||
NoLog bool `json:"-"`
|
NoLog bool `json:"-"`
|
||||||
NoSigs bool `json:"-"`
|
NoSigs bool `json:"-"`
|
||||||
@@ -312,6 +336,7 @@ type Options struct {
|
|||||||
NoSystemAccount bool `json:"-"`
|
NoSystemAccount bool `json:"-"`
|
||||||
Username string `json:"-"`
|
Username string `json:"-"`
|
||||||
Password string `json:"-"`
|
Password string `json:"-"`
|
||||||
|
ProxyRequired bool `json:"-"`
|
||||||
Authorization string `json:"-"`
|
Authorization string `json:"-"`
|
||||||
AuthCallout *AuthCallout `json:"-"`
|
AuthCallout *AuthCallout `json:"-"`
|
||||||
PingInterval time.Duration `json:"ping_interval"`
|
PingInterval time.Duration `json:"ping_interval"`
|
||||||
@@ -329,7 +354,7 @@ type Options struct {
|
|||||||
Gateway GatewayOpts `json:"gateway,omitempty"`
|
Gateway GatewayOpts `json:"gateway,omitempty"`
|
||||||
LeafNode LeafNodeOpts `json:"leaf,omitempty"`
|
LeafNode LeafNodeOpts `json:"leaf,omitempty"`
|
||||||
JetStream bool `json:"jetstream"`
|
JetStream bool `json:"jetstream"`
|
||||||
JetStreamStrict bool `json:"-"`
|
NoJetStreamStrict bool `json:"-"` // Strict by default.
|
||||||
JetStreamMaxMemory int64 `json:"-"`
|
JetStreamMaxMemory int64 `json:"-"`
|
||||||
JetStreamMaxStore int64 `json:"-"`
|
JetStreamMaxStore int64 `json:"-"`
|
||||||
JetStreamDomain string `json:"-"`
|
JetStreamDomain string `json:"-"`
|
||||||
@@ -426,10 +451,16 @@ type Options struct {
|
|||||||
// and used as a filter criteria for some system requests.
|
// and used as a filter criteria for some system requests.
|
||||||
Tags jwt.TagList `json:"-"`
|
Tags jwt.TagList `json:"-"`
|
||||||
|
|
||||||
|
// Metadata describing the server. They will be included in 'Z' responses.
|
||||||
|
Metadata map[string]string `json:"-"`
|
||||||
|
|
||||||
// OCSPConfig enables OCSP Stapling in the server.
|
// OCSPConfig enables OCSP Stapling in the server.
|
||||||
OCSPConfig *OCSPConfig
|
OCSPConfig *OCSPConfig
|
||||||
tlsConfigOpts *TLSConfigOpts
|
tlsConfigOpts *TLSConfigOpts
|
||||||
|
|
||||||
|
// Proxies configuration.
|
||||||
|
Proxies *ProxiesConfig
|
||||||
|
|
||||||
// private fields, used to know if bool options are explicitly
|
// private fields, used to know if bool options are explicitly
|
||||||
// defined in config and/or command line params.
|
// defined in config and/or command line params.
|
||||||
inConfig map[string]bool
|
inConfig map[string]bool
|
||||||
@@ -714,6 +745,8 @@ type authorization struct {
|
|||||||
token string
|
token string
|
||||||
nkey string
|
nkey string
|
||||||
acc string
|
acc string
|
||||||
|
// If connection must come through proxy
|
||||||
|
proxyRequired bool
|
||||||
// Multiple Nkeys/Users
|
// Multiple Nkeys/Users
|
||||||
nkeys []*NkeyUser
|
nkeys []*NkeyUser
|
||||||
users []*User
|
users []*User
|
||||||
@@ -737,6 +770,7 @@ type TLSConfigOpts struct {
|
|||||||
FallbackDelay time.Duration // Where supported, indicates how long to wait for the handshake before falling back to sending the INFO protocol first.
|
FallbackDelay time.Duration // Where supported, indicates how long to wait for the handshake before falling back to sending the INFO protocol first.
|
||||||
Timeout float64
|
Timeout float64
|
||||||
RateLimit int64
|
RateLimit int64
|
||||||
|
AllowInsecureCiphers bool
|
||||||
Ciphers []uint16
|
Ciphers []uint16
|
||||||
CurvePreferences []tls.CurveID
|
CurvePreferences []tls.CurveID
|
||||||
PinnedCerts PinnedCertSet
|
PinnedCerts PinnedCertSet
|
||||||
@@ -765,6 +799,17 @@ type OCSPConfig struct {
|
|||||||
OverrideURLs []string
|
OverrideURLs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProxiesConfig represents the options of Proxies.
|
||||||
|
type ProxiesConfig struct {
|
||||||
|
Trusted []*ProxyConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyConfig represents the options of Proxy.
|
||||||
|
type ProxyConfig struct {
|
||||||
|
// Public key.
|
||||||
|
Key string
|
||||||
|
}
|
||||||
|
|
||||||
var tlsUsage = `
|
var tlsUsage = `
|
||||||
TLS configuration is specified in the tls section of a configuration file:
|
TLS configuration is specified in the tls section of a configuration file:
|
||||||
|
|
||||||
@@ -1047,6 +1092,7 @@ func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnin
|
|||||||
o.authBlockDefined = true
|
o.authBlockDefined = true
|
||||||
o.Username = auth.user
|
o.Username = auth.user
|
||||||
o.Password = auth.pass
|
o.Password = auth.pass
|
||||||
|
o.ProxyRequired = auth.proxyRequired
|
||||||
o.Authorization = auth.token
|
o.Authorization = auth.token
|
||||||
o.AuthTimeout = auth.timeout
|
o.AuthTimeout = auth.timeout
|
||||||
o.AuthCallout = auth.callout
|
o.AuthCallout = auth.callout
|
||||||
@@ -1649,6 +1695,24 @@ func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnin
|
|||||||
*errors = append(*errors, err)
|
*errors = append(*errors, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "server_metadata":
|
||||||
|
var err error
|
||||||
|
switch v := v.(type) {
|
||||||
|
case map[string]any:
|
||||||
|
for mk, mv := range v {
|
||||||
|
tk, mv = unwrapValue(mv, <)
|
||||||
|
if o.Metadata == nil {
|
||||||
|
o.Metadata = make(map[string]string)
|
||||||
|
}
|
||||||
|
o.Metadata[mk] = mv.(string)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = &configErr{tk, fmt.Sprintf("error parsing metadata: unsupported type %T", v)}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
*errors = append(*errors, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
case "default_js_domain":
|
case "default_js_domain":
|
||||||
vv, ok := v.(map[string]any)
|
vv, ok := v.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -1693,6 +1757,13 @@ func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnin
|
|||||||
o.NoFastProducerStall = v.(bool)
|
o.NoFastProducerStall = v.(bool)
|
||||||
case "max_closed_clients":
|
case "max_closed_clients":
|
||||||
o.MaxClosedClients = int(v.(int64))
|
o.MaxClosedClients = int(v.(int64))
|
||||||
|
case "proxies":
|
||||||
|
proxies, err := parseProxies(tk, errors)
|
||||||
|
if err != nil {
|
||||||
|
*errors = append(*errors, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
o.Proxies = proxies
|
||||||
default:
|
default:
|
||||||
if au := atomic.LoadInt32(&allowUnknownTopLevelField); au == 0 && !tk.IsUsedVariable() {
|
if au := atomic.LoadInt32(&allowUnknownTopLevelField); au == 0 && !tk.IsUsedVariable() {
|
||||||
err := &unknownConfigFieldErr{
|
err := &unknownConfigFieldErr{
|
||||||
@@ -1883,6 +1954,8 @@ func parseCluster(v any, opts *Options, errors *[]error, warnings *[]error) erro
|
|||||||
trackExplicitVal(&opts.inConfig, "Cluster.NoAdvertise", opts.Cluster.NoAdvertise)
|
trackExplicitVal(&opts.inConfig, "Cluster.NoAdvertise", opts.Cluster.NoAdvertise)
|
||||||
case "connect_retries":
|
case "connect_retries":
|
||||||
opts.Cluster.ConnectRetries = int(mv.(int64))
|
opts.Cluster.ConnectRetries = int(mv.(int64))
|
||||||
|
case "connect_backoff":
|
||||||
|
opts.Cluster.ConnectBackoff = mv.(bool)
|
||||||
case "permissions":
|
case "permissions":
|
||||||
perms, err := parseUserPermissions(mv, errors)
|
perms, err := parseUserPermissions(mv, errors)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2091,6 +2164,8 @@ func parseGateway(v any, o *Options, errors *[]error, warnings *[]error) error {
|
|||||||
o.Gateway.Advertise = mv.(string)
|
o.Gateway.Advertise = mv.(string)
|
||||||
case "connect_retries":
|
case "connect_retries":
|
||||||
o.Gateway.ConnectRetries = int(mv.(int64))
|
o.Gateway.ConnectRetries = int(mv.(int64))
|
||||||
|
case "connect_backoff":
|
||||||
|
o.Gateway.ConnectBackoff = mv.(bool)
|
||||||
case "gateways":
|
case "gateways":
|
||||||
gateways, err := parseGateways(mv, errors, warnings)
|
gateways, err := parseGateways(mv, errors, warnings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2265,7 +2340,7 @@ func parseJetStreamLimits(v any, opts *Options, errors *[]error) error {
|
|||||||
var lt token
|
var lt token
|
||||||
tk, v := unwrapValue(v, <)
|
tk, v := unwrapValue(v, <)
|
||||||
|
|
||||||
lim := JSLimitOpts{}
|
opts.JetStreamLimits = JSLimitOpts{}
|
||||||
|
|
||||||
vv, ok := v.(map[string]any)
|
vv, ok := v.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -2275,14 +2350,57 @@ func parseJetStreamLimits(v any, opts *Options, errors *[]error) error {
|
|||||||
tk, mv = unwrapValue(mv, <)
|
tk, mv = unwrapValue(mv, <)
|
||||||
switch strings.ToLower(mk) {
|
switch strings.ToLower(mk) {
|
||||||
case "max_ack_pending":
|
case "max_ack_pending":
|
||||||
lim.MaxAckPending = int(mv.(int64))
|
opts.JetStreamLimits.MaxAckPending = int(mv.(int64))
|
||||||
case "max_ha_assets":
|
case "max_ha_assets":
|
||||||
lim.MaxHAAssets = int(mv.(int64))
|
opts.JetStreamLimits.MaxHAAssets = int(mv.(int64))
|
||||||
case "max_request_batch":
|
case "max_request_batch":
|
||||||
lim.MaxRequestBatch = int(mv.(int64))
|
opts.JetStreamLimits.MaxRequestBatch = int(mv.(int64))
|
||||||
case "duplicate_window":
|
case "duplicate_window":
|
||||||
var err error
|
var err error
|
||||||
lim.Duplicates, err = time.ParseDuration(mv.(string))
|
opts.JetStreamLimits.Duplicates, err = time.ParseDuration(mv.(string))
|
||||||
|
if err != nil {
|
||||||
|
*errors = append(*errors, err)
|
||||||
|
}
|
||||||
|
case "batch":
|
||||||
|
if err := parseJetStreamLimitsBatch(tk, opts, errors); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if !tk.IsUsedVariable() {
|
||||||
|
err := &unknownConfigFieldErr{
|
||||||
|
field: mk,
|
||||||
|
configErr: configErr{
|
||||||
|
token: tk,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
*errors = append(*errors, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseJetStreamLimitsBatch(v any, opts *Options, errors *[]error) error {
|
||||||
|
var lt token
|
||||||
|
tk, v := unwrapValue(v, <)
|
||||||
|
|
||||||
|
vv, ok := v.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return &configErr{tk, fmt.Sprintf("Expected a map to define batch limits, got %T", v)}
|
||||||
|
}
|
||||||
|
for mk, mv := range vv {
|
||||||
|
tk, mv = unwrapValue(mv, <)
|
||||||
|
switch strings.ToLower(mk) {
|
||||||
|
case "max_inflight_per_stream":
|
||||||
|
opts.JetStreamLimits.MaxBatchInflightPerStream = int(mv.(int64))
|
||||||
|
case "max_inflight_total":
|
||||||
|
opts.JetStreamLimits.MaxBatchInflightTotal = int(mv.(int64))
|
||||||
|
case "max_msgs":
|
||||||
|
opts.JetStreamLimits.MaxBatchSize = int(mv.(int64))
|
||||||
|
case "timeout":
|
||||||
|
var err error
|
||||||
|
opts.JetStreamLimits.MaxBatchTimeout, err = time.ParseDuration(mv.(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
*errors = append(*errors, err)
|
*errors = append(*errors, err)
|
||||||
}
|
}
|
||||||
@@ -2299,7 +2417,6 @@ func parseJetStreamLimits(v any, opts *Options, errors *[]error) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
opts.JetStreamLimits = lim
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2308,7 +2425,7 @@ func parseJetStreamTPM(v interface{}, opts *Options, errors *[]error) error {
|
|||||||
var lt token
|
var lt token
|
||||||
tk, v := unwrapValue(v, <)
|
tk, v := unwrapValue(v, <)
|
||||||
|
|
||||||
tpm := JSTpmOpts{}
|
opts.JetStreamTpm = JSTpmOpts{}
|
||||||
|
|
||||||
vv, ok := v.(map[string]interface{})
|
vv, ok := v.(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -2318,13 +2435,13 @@ func parseJetStreamTPM(v interface{}, opts *Options, errors *[]error) error {
|
|||||||
tk, mv = unwrapValue(mv, <)
|
tk, mv = unwrapValue(mv, <)
|
||||||
switch strings.ToLower(mk) {
|
switch strings.ToLower(mk) {
|
||||||
case "keys_file":
|
case "keys_file":
|
||||||
tpm.KeysFile = mv.(string)
|
opts.JetStreamTpm.KeysFile = mv.(string)
|
||||||
case "encryption_password":
|
case "encryption_password":
|
||||||
tpm.KeyPassword = mv.(string)
|
opts.JetStreamTpm.KeyPassword = mv.(string)
|
||||||
case "srk_password":
|
case "srk_password":
|
||||||
tpm.SrkPassword = mv.(string)
|
opts.JetStreamTpm.SrkPassword = mv.(string)
|
||||||
case "pcr":
|
case "pcr":
|
||||||
tpm.Pcr = int(mv.(int64))
|
opts.JetStreamTpm.Pcr = int(mv.(int64))
|
||||||
case "cipher":
|
case "cipher":
|
||||||
if err := setJetStreamEkCipher(opts, mv, tk); err != nil {
|
if err := setJetStreamEkCipher(opts, mv, tk); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -2342,7 +2459,6 @@ func parseJetStreamTPM(v interface{}, opts *Options, errors *[]error) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
opts.JetStreamTpm = tpm
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2384,7 +2500,7 @@ func parseJetStream(v any, opts *Options, errors *[]error, warnings *[]error) er
|
|||||||
switch strings.ToLower(mk) {
|
switch strings.ToLower(mk) {
|
||||||
case "strict":
|
case "strict":
|
||||||
if v, ok := mv.(bool); ok {
|
if v, ok := mv.(bool); ok {
|
||||||
opts.JetStreamStrict = v
|
opts.NoJetStreamStrict = !v
|
||||||
} else {
|
} else {
|
||||||
return &configErr{tk, fmt.Sprintf("Expected 'true' or 'false' for bool value, got '%s'", mv)}
|
return &configErr{tk, fmt.Sprintf("Expected 'true' or 'false' for bool value, got '%s'", mv)}
|
||||||
}
|
}
|
||||||
@@ -2521,6 +2637,7 @@ func parseLeafNodes(v any, opts *Options, errors *[]error, warnings *[]error) er
|
|||||||
}
|
}
|
||||||
opts.LeafNode.Username = auth.user
|
opts.LeafNode.Username = auth.user
|
||||||
opts.LeafNode.Password = auth.pass
|
opts.LeafNode.Password = auth.pass
|
||||||
|
opts.LeafNode.ProxyRequired = auth.proxyRequired
|
||||||
opts.LeafNode.AuthTimeout = auth.timeout
|
opts.LeafNode.AuthTimeout = auth.timeout
|
||||||
opts.LeafNode.Account = auth.acc
|
opts.LeafNode.Account = auth.acc
|
||||||
opts.LeafNode.Users = auth.users
|
opts.LeafNode.Users = auth.users
|
||||||
@@ -2575,6 +2692,8 @@ func parseLeafNodes(v any, opts *Options, errors *[]error, warnings *[]error) er
|
|||||||
*errors = append(*errors, err)
|
*errors = append(*errors, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
case "isolate_leafnode_interest", "isolate":
|
||||||
|
opts.LeafNode.IsolateLeafnodeInterest = mv.(bool)
|
||||||
default:
|
default:
|
||||||
if !tk.IsUsedVariable() {
|
if !tk.IsUsedVariable() {
|
||||||
err := &unknownConfigFieldErr{
|
err := &unknownConfigFieldErr{
|
||||||
@@ -2647,6 +2766,8 @@ func parseLeafAuthorization(v any, errors, warnings *[]error) (*authorization, e
|
|||||||
auth.users = users
|
auth.users = users
|
||||||
case "account":
|
case "account":
|
||||||
auth.acc = mv.(string)
|
auth.acc = mv.(string)
|
||||||
|
case "proxy_required":
|
||||||
|
auth.proxyRequired = mv.(bool)
|
||||||
default:
|
default:
|
||||||
if !tk.IsUsedVariable() {
|
if !tk.IsUsedVariable() {
|
||||||
err := &unknownConfigFieldErr{
|
err := &unknownConfigFieldErr{
|
||||||
@@ -2705,6 +2826,8 @@ func parseLeafUsers(mv any, errors *[]error) ([]*User, error) {
|
|||||||
// we need to create internal objects to store u/p and account
|
// we need to create internal objects to store u/p and account
|
||||||
// name and have a server structure to hold that.
|
// name and have a server structure to hold that.
|
||||||
user.Account = NewAccount(v.(string))
|
user.Account = NewAccount(v.(string))
|
||||||
|
case "proxy_required":
|
||||||
|
user.ProxyRequired = v.(bool)
|
||||||
default:
|
default:
|
||||||
if !tk.IsUsedVariable() {
|
if !tk.IsUsedVariable() {
|
||||||
err := &unknownConfigFieldErr{
|
err := &unknownConfigFieldErr{
|
||||||
@@ -2854,6 +2977,10 @@ func parseRemoteLeafNodes(v any, errors *[]error, warnings *[]error) ([]*RemoteL
|
|||||||
default:
|
default:
|
||||||
*errors = append(*errors, &configErr{tk, fmt.Sprintf("Expected boolean or map for jetstream_cluster_migrate, got %T", v)})
|
*errors = append(*errors, &configErr{tk, fmt.Sprintf("Expected boolean or map for jetstream_cluster_migrate, got %T", v)})
|
||||||
}
|
}
|
||||||
|
case "isolate_leafnode_interest", "isolate":
|
||||||
|
remote.LocalIsolation = v.(bool)
|
||||||
|
case "request_isolation":
|
||||||
|
remote.RequestIsolation = v.(bool)
|
||||||
case "compression":
|
case "compression":
|
||||||
if err := parseCompression(&remote.Compression, CompressionS2Auto, tk, k, v); err != nil {
|
if err := parseCompression(&remote.Compression, CompressionS2Auto, tk, k, v); err != nil {
|
||||||
*errors = append(*errors, err)
|
*errors = append(*errors, err)
|
||||||
@@ -2861,6 +2988,8 @@ func parseRemoteLeafNodes(v any, errors *[]error, warnings *[]error) ([]*RemoteL
|
|||||||
}
|
}
|
||||||
case "first_info_timeout":
|
case "first_info_timeout":
|
||||||
remote.FirstInfoTimeout = parseDuration(k, tk, v, errors, warnings)
|
remote.FirstInfoTimeout = parseDuration(k, tk, v, errors, warnings)
|
||||||
|
case "disabled":
|
||||||
|
remote.Disabled = v.(bool)
|
||||||
default:
|
default:
|
||||||
if !tk.IsUsedVariable() {
|
if !tk.IsUsedVariable() {
|
||||||
err := &unknownConfigFieldErr{
|
err := &unknownConfigFieldErr{
|
||||||
@@ -4192,6 +4321,8 @@ func parseAuthorization(v any, errors, warnings *[]error) (*authorization, error
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
auth.callout = ac
|
auth.callout = ac
|
||||||
|
case "proxy_required":
|
||||||
|
auth.proxyRequired = mv.(bool)
|
||||||
default:
|
default:
|
||||||
if !tk.IsUsedVariable() {
|
if !tk.IsUsedVariable() {
|
||||||
err := &unknownConfigFieldErr{
|
err := &unknownConfigFieldErr{
|
||||||
@@ -4264,6 +4395,9 @@ func parseUsers(mv any, errors *[]error) ([]*NkeyUser, []*User, error) {
|
|||||||
cts := parseAllowedConnectionTypes(tk, <, v, errors)
|
cts := parseAllowedConnectionTypes(tk, <, v, errors)
|
||||||
nkey.AllowedConnectionTypes = cts
|
nkey.AllowedConnectionTypes = cts
|
||||||
user.AllowedConnectionTypes = cts
|
user.AllowedConnectionTypes = cts
|
||||||
|
case "proxy_required":
|
||||||
|
nkey.ProxyRequired = v.(bool)
|
||||||
|
user.ProxyRequired = v.(bool)
|
||||||
default:
|
default:
|
||||||
if !tk.IsUsedVariable() {
|
if !tk.IsUsedVariable() {
|
||||||
err := &unknownConfigFieldErr{
|
err := &unknownConfigFieldErr{
|
||||||
@@ -4639,12 +4773,11 @@ func PrintTLSHelpAndDie() {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCipher(cipherName string) (uint16, error) {
|
func parseCipher(cipherName string) (*tls.CipherSuite, error) {
|
||||||
cipher, exists := cipherMap[cipherName]
|
cipher, exists := cipherMap[cipherName]
|
||||||
if !exists {
|
if !exists {
|
||||||
return 0, fmt.Errorf("unrecognized cipher %s", cipherName)
|
return nil, fmt.Errorf("unrecognized cipher %s", cipherName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cipher, nil
|
return cipher, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4680,6 +4813,7 @@ func parseTLS(v any, isClientCtx bool) (t *TLSConfigOpts, retErr error) {
|
|||||||
tlsm map[string]any
|
tlsm map[string]any
|
||||||
tc = TLSConfigOpts{}
|
tc = TLSConfigOpts{}
|
||||||
lt token
|
lt token
|
||||||
|
ics []*tls.CipherSuite // Insecure ciphers found
|
||||||
)
|
)
|
||||||
defer convertPanicToError(<, &retErr)
|
defer convertPanicToError(<, &retErr)
|
||||||
|
|
||||||
@@ -4739,6 +4873,12 @@ func parseTLS(v any, isClientCtx bool) (t *TLSConfigOpts, retErr error) {
|
|||||||
tc.Verify = verify
|
tc.Verify = verify
|
||||||
}
|
}
|
||||||
tc.TLSCheckKnownURLs = verify
|
tc.TLSCheckKnownURLs = verify
|
||||||
|
case "allow_insecure_cipher_suites":
|
||||||
|
allow, ok := mv.(bool)
|
||||||
|
if !ok {
|
||||||
|
return nil, &configErr{tk, "error parsing tls config, expected 'allow_insecure_cipher_suites' to be a boolean"}
|
||||||
|
}
|
||||||
|
tc.AllowInsecureCiphers = allow
|
||||||
case "cipher_suites":
|
case "cipher_suites":
|
||||||
ra := mv.([]any)
|
ra := mv.([]any)
|
||||||
if len(ra) == 0 {
|
if len(ra) == 0 {
|
||||||
@@ -4751,7 +4891,10 @@ func parseTLS(v any, isClientCtx bool) (t *TLSConfigOpts, retErr error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &configErr{tk, err.Error()}
|
return nil, &configErr{tk, err.Error()}
|
||||||
}
|
}
|
||||||
tc.Ciphers = append(tc.Ciphers, cipher)
|
tc.Ciphers = append(tc.Ciphers, cipher.ID)
|
||||||
|
if cipher.Insecure {
|
||||||
|
ics = append(ics, cipher)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "curve_preferences":
|
case "curve_preferences":
|
||||||
ra := mv.([]any)
|
ra := mv.([]any)
|
||||||
@@ -4969,6 +5112,16 @@ func parseTLS(v any, isClientCtx bool) (t *TLSConfigOpts, retErr error) {
|
|||||||
tc.CurvePreferences = defaultCurvePreferences()
|
tc.CurvePreferences = defaultCurvePreferences()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we don't allow insecure ciphers, and yet some were configured, then we
|
||||||
|
// should error.
|
||||||
|
if !tc.AllowInsecureCiphers && len(ics) > 0 {
|
||||||
|
names := make([]string, 0, len(ics))
|
||||||
|
for _, ic := range ics {
|
||||||
|
names = append(names, ic.Name)
|
||||||
|
}
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf("insecure cipher suites configured without 'allow_insecure_cipher_suites' option set: %s", strings.Join(names, ", "))}
|
||||||
|
}
|
||||||
|
|
||||||
return &tc, nil
|
return &tc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5258,6 +5411,95 @@ func parseMQTT(v any, o *Options, errors *[]error, warnings *[]error) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseProxies(mv any, errors *[]error) (*ProxiesConfig, error) {
|
||||||
|
var (
|
||||||
|
tk token
|
||||||
|
lt token
|
||||||
|
proxies = &ProxiesConfig{}
|
||||||
|
)
|
||||||
|
defer convertPanicToErrorList(<, errors)
|
||||||
|
|
||||||
|
tk, mv = unwrapValue(mv, <)
|
||||||
|
pm, ok := mv.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf("expected proxies to be a map/struct, got %T", mv)}
|
||||||
|
}
|
||||||
|
for mk, mv := range pm {
|
||||||
|
tk, _ = unwrapValue(mv, <)
|
||||||
|
|
||||||
|
switch strings.ToLower(mk) {
|
||||||
|
case "trusted":
|
||||||
|
trusted, err := parseProxiesTrusted(tk, errors)
|
||||||
|
if err != nil {
|
||||||
|
*errors = append(*errors, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
proxies.Trusted = trusted
|
||||||
|
default:
|
||||||
|
if !tk.IsUsedVariable() {
|
||||||
|
err := &unknownConfigFieldErr{
|
||||||
|
field: mk,
|
||||||
|
configErr: configErr{
|
||||||
|
token: tk,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
*errors = append(*errors, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return proxies, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseProxiesTrusted(mv any, errors *[]error) ([]*ProxyConfig, error) {
|
||||||
|
var (
|
||||||
|
tk token
|
||||||
|
lt token
|
||||||
|
trusted []*ProxyConfig
|
||||||
|
)
|
||||||
|
defer convertPanicToErrorList(<, errors)
|
||||||
|
|
||||||
|
tk, mv = unwrapValue(mv, <)
|
||||||
|
ta, ok := mv.([]any)
|
||||||
|
if !ok {
|
||||||
|
return nil, &configErr{tk, fmt.Sprintf("expected proxies' trusted field to be an array, got %T", mv)}
|
||||||
|
}
|
||||||
|
for _, t := range ta {
|
||||||
|
tk, t = unwrapValue(t, <)
|
||||||
|
// Check its a map/struct
|
||||||
|
tm, ok := t.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
err := &configErr{tk, fmt.Sprintf("expected proxies' trusted entry to be a map/struct, got %T", t)}
|
||||||
|
*errors = append(*errors, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
proxy := &ProxyConfig{}
|
||||||
|
for k, v := range tm {
|
||||||
|
tk, v = unwrapValue(v, <)
|
||||||
|
switch strings.ToLower(k) {
|
||||||
|
case "key", "public_key":
|
||||||
|
proxy.Key = v.(string)
|
||||||
|
if !nkeys.IsValidPublicKey(proxy.Key) {
|
||||||
|
*errors = append(*errors, &configErr{tk, fmt.Sprintf("invalid proxy key %q", proxy.Key)})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if !tk.IsUsedVariable() {
|
||||||
|
err := &unknownConfigFieldErr{
|
||||||
|
field: k,
|
||||||
|
configErr: configErr{
|
||||||
|
token: tk,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
*errors = append(*errors, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trusted = append(trusted, proxy)
|
||||||
|
}
|
||||||
|
return trusted, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GenTLSConfig loads TLS related configuration parameters.
|
// GenTLSConfig loads TLS related configuration parameters.
|
||||||
func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) {
|
func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) {
|
||||||
// Create the tls.Config from our options before including the certs.
|
// Create the tls.Config from our options before including the certs.
|
||||||
|
|||||||
+202
-80
@@ -45,6 +45,7 @@ type RaftNode interface {
|
|||||||
SendSnapshot(snap []byte) error
|
SendSnapshot(snap []byte) error
|
||||||
NeedSnapshot() bool
|
NeedSnapshot() bool
|
||||||
Applied(index uint64) (entries uint64, bytes uint64)
|
Applied(index uint64) (entries uint64, bytes uint64)
|
||||||
|
Processed(index uint64, applied uint64) (entries uint64, bytes uint64)
|
||||||
State() RaftState
|
State() RaftState
|
||||||
Size() (entries, bytes uint64)
|
Size() (entries, bytes uint64)
|
||||||
Progress() (index, commit, applied uint64)
|
Progress() (index, commit, applied uint64)
|
||||||
@@ -98,7 +99,7 @@ type WAL interface {
|
|||||||
State() StreamState
|
State() StreamState
|
||||||
FastState(*StreamState)
|
FastState(*StreamState)
|
||||||
Stop() error
|
Stop() error
|
||||||
Delete() error
|
Delete(inline bool) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
@@ -168,12 +169,13 @@ type raft struct {
|
|||||||
llqrt time.Time // Last quorum lost time
|
llqrt time.Time // Last quorum lost time
|
||||||
lsut time.Time // Last scale-up time
|
lsut time.Time // Last scale-up time
|
||||||
|
|
||||||
term uint64 // The current vote term
|
term uint64 // The current vote term
|
||||||
pterm uint64 // Previous term from the last snapshot
|
pterm uint64 // Previous term from the last snapshot
|
||||||
pindex uint64 // Previous index from the last snapshot
|
pindex uint64 // Previous index from the last snapshot
|
||||||
commit uint64 // Index of the most recent commit
|
commit uint64 // Index of the most recent commit
|
||||||
applied uint64 // Index of the most recently applied commit
|
processed uint64 // Index of the most recently processed commit
|
||||||
papplied uint64 // First sequence of our log, matches when we last installed a snapshot.
|
applied uint64 // Index of the most recently applied commit
|
||||||
|
papplied uint64 // First sequence of our log, matches when we last installed a snapshot.
|
||||||
|
|
||||||
aflr uint64 // Index when to signal initial messages have been applied after becoming leader. 0 means signaling is disabled.
|
aflr uint64 // Index when to signal initial messages have been applied after becoming leader. 0 means signaling is disabled.
|
||||||
|
|
||||||
@@ -220,12 +222,13 @@ type raft struct {
|
|||||||
leadc chan bool // Leader changes
|
leadc chan bool // Leader changes
|
||||||
quit chan struct{} // Raft group shutdown
|
quit chan struct{} // Raft group shutdown
|
||||||
|
|
||||||
lxfer bool // Are we doing a leadership transfer?
|
lxfer bool // Are we doing a leadership transfer?
|
||||||
hcbehind bool // Were we falling behind at the last health check? (see: isCurrent)
|
hcbehind bool // Were we falling behind at the last health check? (see: isCurrent)
|
||||||
maybeLeader bool // The group had a preferred leader. And is maybe already acting as leader prior to scale up.
|
maybeLeader bool // The group had a preferred leader. And is maybe already acting as leader prior to scale up.
|
||||||
paused bool // Whether or not applies are paused
|
paused bool // Whether or not applies are paused
|
||||||
observer bool // The node is observing, i.e. not able to become leader
|
observer bool // The node is observing, i.e. not able to become leader
|
||||||
pobserver bool // Were we previously an observer?
|
initializing bool // The node is new, and "empty log" checks can be temporarily relaxed.
|
||||||
|
scaleUp bool // The node is part of a scale up, puts us in observer mode until the log contains data.
|
||||||
}
|
}
|
||||||
|
|
||||||
type proposedEntry struct {
|
type proposedEntry struct {
|
||||||
@@ -281,6 +284,16 @@ type RaftConfig struct {
|
|||||||
Log WAL
|
Log WAL
|
||||||
Track bool
|
Track bool
|
||||||
Observer bool
|
Observer bool
|
||||||
|
|
||||||
|
// Recovering must be set for a Raft group that's recovering after a restart, or if it's
|
||||||
|
// first seen after a catchup from another server. If a server recovers with an empty log,
|
||||||
|
// we know to protect against data loss.
|
||||||
|
Recovering bool
|
||||||
|
|
||||||
|
// ScaleUp identifies the Raft peer set is being scaled up.
|
||||||
|
// We need to protect against losing state due to the new peers starting with an empty log.
|
||||||
|
// Therefore, these empty servers can't try to become leader until they at least have _some_ state.
|
||||||
|
ScaleUp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -539,6 +552,17 @@ func (s *Server) initRaftNode(accName string, cfg *RaftConfig, labels pprofLabel
|
|||||||
n.Lock()
|
n.Lock()
|
||||||
n.resetElectionTimeout()
|
n.resetElectionTimeout()
|
||||||
n.llqrt = time.Now()
|
n.llqrt = time.Now()
|
||||||
|
|
||||||
|
// If our log is empty, and we're initializing, relax the "empty log" checks temporarily.
|
||||||
|
if !cfg.Recovering && n.pindex == 0 {
|
||||||
|
n.initializing = true
|
||||||
|
// If we're scaling up and our log is empty, must put ourselves into observer
|
||||||
|
// and wait for data from the leader.
|
||||||
|
if !cfg.Observer && cfg.ScaleUp {
|
||||||
|
n.scaleUp = true
|
||||||
|
n.setObserverLocked(true, extUndetermined)
|
||||||
|
}
|
||||||
|
}
|
||||||
n.Unlock()
|
n.Unlock()
|
||||||
|
|
||||||
// Register the Raft group.
|
// Register the Raft group.
|
||||||
@@ -842,12 +866,13 @@ func (s *Server) transferRaftLeaders() bool {
|
|||||||
// Propose will propose a new entry to the group.
|
// Propose will propose a new entry to the group.
|
||||||
// This should only be called on the leader.
|
// This should only be called on the leader.
|
||||||
func (n *raft) Propose(data []byte) error {
|
func (n *raft) Propose(data []byte) error {
|
||||||
|
n.Lock()
|
||||||
|
defer n.Unlock()
|
||||||
|
// Check state under lock, we might not be leader anymore.
|
||||||
if state := n.State(); state != Leader {
|
if state := n.State(); state != Leader {
|
||||||
n.debug("Proposal ignored, not leader (state: %v)", state)
|
n.debug("Proposal ignored, not leader (state: %v)", state)
|
||||||
return errNotLeader
|
return errNotLeader
|
||||||
}
|
}
|
||||||
n.Lock()
|
|
||||||
defer n.Unlock()
|
|
||||||
|
|
||||||
// Error if we had a previous write error.
|
// Error if we had a previous write error.
|
||||||
if werr := n.werr; werr != nil {
|
if werr := n.werr; werr != nil {
|
||||||
@@ -857,15 +882,16 @@ func (n *raft) Propose(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProposeDirect will propose multiple entries at once.
|
// ProposeMulti will propose multiple entries at once.
|
||||||
// This should only be called on the leader.
|
// This should only be called on the leader.
|
||||||
func (n *raft) ProposeMulti(entries []*Entry) error {
|
func (n *raft) ProposeMulti(entries []*Entry) error {
|
||||||
if state := n.State(); state != Leader {
|
|
||||||
n.debug("Direct proposal ignored, not leader (state: %v)", state)
|
|
||||||
return errNotLeader
|
|
||||||
}
|
|
||||||
n.Lock()
|
n.Lock()
|
||||||
defer n.Unlock()
|
defer n.Unlock()
|
||||||
|
// Check state under lock, we might not be leader anymore.
|
||||||
|
if state := n.State(); state != Leader {
|
||||||
|
n.debug("Multi proposal ignored, not leader (state: %v)", state)
|
||||||
|
return errNotLeader
|
||||||
|
}
|
||||||
|
|
||||||
// Error if we had a previous write error.
|
// Error if we had a previous write error.
|
||||||
if werr := n.werr; werr != nil {
|
if werr := n.werr; werr != nil {
|
||||||
@@ -893,10 +919,12 @@ func (n *raft) ForwardProposal(entry []byte) error {
|
|||||||
|
|
||||||
// ProposeAddPeer is called to add a peer to the group.
|
// ProposeAddPeer is called to add a peer to the group.
|
||||||
func (n *raft) ProposeAddPeer(peer string) error {
|
func (n *raft) ProposeAddPeer(peer string) error {
|
||||||
|
n.RLock()
|
||||||
|
// Check state under lock, we might not be leader anymore.
|
||||||
if n.State() != Leader {
|
if n.State() != Leader {
|
||||||
|
n.RUnlock()
|
||||||
return errNotLeader
|
return errNotLeader
|
||||||
}
|
}
|
||||||
n.RLock()
|
|
||||||
// Error if we had a previous write error.
|
// Error if we had a previous write error.
|
||||||
if werr := n.werr; werr != nil {
|
if werr := n.werr; werr != nil {
|
||||||
n.RUnlock()
|
n.RUnlock()
|
||||||
@@ -984,10 +1012,13 @@ func (n *raft) AdjustBootClusterSize(csz int) error {
|
|||||||
// AdjustClusterSize will change the cluster set size.
|
// AdjustClusterSize will change the cluster set size.
|
||||||
// Must be the leader.
|
// Must be the leader.
|
||||||
func (n *raft) AdjustClusterSize(csz int) error {
|
func (n *raft) AdjustClusterSize(csz int) error {
|
||||||
|
n.Lock()
|
||||||
|
defer n.Unlock()
|
||||||
|
|
||||||
|
// Check state under lock, we might not be leader anymore.
|
||||||
if n.State() != Leader {
|
if n.State() != Leader {
|
||||||
return errNotLeader
|
return errNotLeader
|
||||||
}
|
}
|
||||||
n.Lock()
|
|
||||||
// Same floor as bootstrap.
|
// Same floor as bootstrap.
|
||||||
if csz < 2 {
|
if csz < 2 {
|
||||||
csz = 2
|
csz = 2
|
||||||
@@ -997,7 +1028,6 @@ func (n *raft) AdjustClusterSize(csz int) error {
|
|||||||
// a quorum.
|
// a quorum.
|
||||||
n.csz = csz
|
n.csz = csz
|
||||||
n.qn = n.csz/2 + 1
|
n.qn = n.csz/2 + 1
|
||||||
n.Unlock()
|
|
||||||
|
|
||||||
n.sendPeerState()
|
n.sendPeerState()
|
||||||
return nil
|
return nil
|
||||||
@@ -1023,7 +1053,6 @@ func (n *raft) PauseApply() error {
|
|||||||
n.paused = true
|
n.paused = true
|
||||||
n.hcommit = n.commit
|
n.hcommit = n.commit
|
||||||
// Also prevent us from trying to become a leader while paused and catching up.
|
// Also prevent us from trying to become a leader while paused and catching up.
|
||||||
n.pobserver, n.observer = n.observer, true
|
|
||||||
n.resetElect(observerModeInterval)
|
n.resetElect(observerModeInterval)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -1066,8 +1095,7 @@ func (n *raft) ResumeApply() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear our observer and paused state after we apply.
|
// Clear our paused state after we apply.
|
||||||
n.observer, n.pobserver = n.pobserver, false
|
|
||||||
n.paused = false
|
n.paused = false
|
||||||
n.hcommit = 0
|
n.hcommit = 0
|
||||||
|
|
||||||
@@ -1084,6 +1112,16 @@ func (n *raft) ResumeApply() {
|
|||||||
// apply queue. It will return the number of entries and an estimation of the
|
// apply queue. It will return the number of entries and an estimation of the
|
||||||
// byte size that could be removed with a snapshot/compact.
|
// byte size that could be removed with a snapshot/compact.
|
||||||
func (n *raft) Applied(index uint64) (entries uint64, bytes uint64) {
|
func (n *raft) Applied(index uint64) (entries uint64, bytes uint64) {
|
||||||
|
return n.Processed(index, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processed is a callback that must be called by the upper layer when it
|
||||||
|
// has processed the committed entries that it received from the apply queue,
|
||||||
|
// but it (maybe) hasn't applied all the processed entries yet.
|
||||||
|
// Used to indicate a commit was processed, even if it wasn't applied yet and
|
||||||
|
// can't be compacted away by a snapshot just yet. Which allows us to try to
|
||||||
|
// become leader if we've processed all commits, even if they're not all applied.
|
||||||
|
func (n *raft) Processed(index uint64, applied uint64) (entries uint64, bytes uint64) {
|
||||||
n.Lock()
|
n.Lock()
|
||||||
defer n.Unlock()
|
defer n.Unlock()
|
||||||
|
|
||||||
@@ -1092,13 +1130,22 @@ func (n *raft) Applied(index uint64) (entries uint64, bytes uint64) {
|
|||||||
return 0, 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore if already applied.
|
// Ignore if already processed.
|
||||||
if index > n.applied {
|
if index > n.processed {
|
||||||
n.applied = index
|
n.processed = index
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it was set, and we reached the minimum applied index, reset and send signal to upper layer.
|
// Ignore if already applied.
|
||||||
if n.aflr > 0 && index >= n.aflr {
|
if applied > index {
|
||||||
|
applied = index
|
||||||
|
}
|
||||||
|
if applied > n.applied {
|
||||||
|
n.applied = applied
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it was set, and we reached the minimum processed index, reset and send signal to upper layer.
|
||||||
|
// We're not waiting for processed AND applied, because applying could take longer.
|
||||||
|
if n.aflr > 0 && n.processed >= n.aflr {
|
||||||
n.aflr = 0
|
n.aflr = 0
|
||||||
// Quick sanity-check to confirm we're still leader.
|
// Quick sanity-check to confirm we're still leader.
|
||||||
// In which case we must signal, since switchToLeader would not have done so already.
|
// In which case we must signal, since switchToLeader would not have done so already.
|
||||||
@@ -1165,7 +1212,10 @@ func (n *raft) encodeSnapshot(snap *snapshot) []byte {
|
|||||||
// Should only be used when the upper layers know this is most recent.
|
// Should only be used when the upper layers know this is most recent.
|
||||||
// Used when restoring streams, moving a stream from R1 to R>1, etc.
|
// Used when restoring streams, moving a stream from R1 to R>1, etc.
|
||||||
func (n *raft) SendSnapshot(data []byte) error {
|
func (n *raft) SendSnapshot(data []byte) error {
|
||||||
n.sendAppendEntry([]*Entry{{EntrySnapshot, data}})
|
n.Lock()
|
||||||
|
defer n.Unlock()
|
||||||
|
// Don't check if we're leader before sending and storing, this is used on scaleup.
|
||||||
|
n.sendAppendEntryLocked([]*Entry{{EntrySnapshot, data}}, false)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1592,11 +1642,12 @@ func (n *raft) selectNextLeader() string {
|
|||||||
|
|
||||||
// StepDown will have a leader stepdown and optionally do a leader transfer.
|
// StepDown will have a leader stepdown and optionally do a leader transfer.
|
||||||
func (n *raft) StepDown(preferred ...string) error {
|
func (n *raft) StepDown(preferred ...string) error {
|
||||||
|
n.Lock()
|
||||||
|
// Check state under lock, we might not be leader anymore.
|
||||||
if n.State() != Leader {
|
if n.State() != Leader {
|
||||||
|
n.Unlock()
|
||||||
return errNotLeader
|
return errNotLeader
|
||||||
}
|
}
|
||||||
|
|
||||||
n.Lock()
|
|
||||||
if len(preferred) > 1 {
|
if len(preferred) > 1 {
|
||||||
n.Unlock()
|
n.Unlock()
|
||||||
return errTooManyPrefs
|
return errTooManyPrefs
|
||||||
@@ -1686,6 +1737,7 @@ func (n *raft) CampaignImmediately() error {
|
|||||||
n.Lock()
|
n.Lock()
|
||||||
defer n.Unlock()
|
defer n.Unlock()
|
||||||
n.maybeLeader = true
|
n.maybeLeader = true
|
||||||
|
n.resetInitializing()
|
||||||
return n.campaign(minCampaignTimeout / 2)
|
return n.campaign(minCampaignTimeout / 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1775,21 +1827,27 @@ func (n *raft) Peers() []*Peer {
|
|||||||
|
|
||||||
// Update and propose our known set of peers.
|
// Update and propose our known set of peers.
|
||||||
func (n *raft) ProposeKnownPeers(knownPeers []string) {
|
func (n *raft) ProposeKnownPeers(knownPeers []string) {
|
||||||
|
n.Lock()
|
||||||
|
defer n.Unlock()
|
||||||
// If we are the leader update and send this update out.
|
// If we are the leader update and send this update out.
|
||||||
if n.State() != Leader {
|
if n.State() != Leader {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
n.UpdateKnownPeers(knownPeers)
|
n.updateKnownPeersLocked(knownPeers)
|
||||||
n.sendPeerState()
|
n.sendPeerState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update our known set of peers.
|
// Update our known set of peers.
|
||||||
func (n *raft) UpdateKnownPeers(knownPeers []string) {
|
func (n *raft) UpdateKnownPeers(knownPeers []string) {
|
||||||
n.Lock()
|
n.Lock()
|
||||||
|
n.updateKnownPeersLocked(knownPeers)
|
||||||
|
n.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *raft) updateKnownPeersLocked(knownPeers []string) {
|
||||||
// Process like peer state update.
|
// Process like peer state update.
|
||||||
ps := &peerState{knownPeers, len(knownPeers), n.extSt}
|
ps := &peerState{knownPeers, len(knownPeers), n.extSt}
|
||||||
n.processPeerState(ps)
|
n.processPeerState(ps)
|
||||||
n.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyQ returns the apply queue that new commits will be sent to for the
|
// ApplyQ returns the apply queue that new commits will be sent to for the
|
||||||
@@ -1826,7 +1884,7 @@ func (n *raft) Delete() {
|
|||||||
defer n.Unlock()
|
defer n.Unlock()
|
||||||
|
|
||||||
if wal := n.wal; wal != nil {
|
if wal := n.wal; wal != nil {
|
||||||
wal.Delete()
|
wal.Delete(false)
|
||||||
}
|
}
|
||||||
os.RemoveAll(n.sd)
|
os.RemoveAll(n.sd)
|
||||||
n.debug("Deleted")
|
n.debug("Deleted")
|
||||||
@@ -2082,15 +2140,10 @@ func (n *raft) SetObserver(isObserver bool) {
|
|||||||
func (n *raft) setObserver(isObserver bool, extSt extensionState) {
|
func (n *raft) setObserver(isObserver bool, extSt extensionState) {
|
||||||
n.Lock()
|
n.Lock()
|
||||||
defer n.Unlock()
|
defer n.Unlock()
|
||||||
|
n.setObserverLocked(isObserver, extSt)
|
||||||
|
}
|
||||||
|
|
||||||
if n.paused {
|
func (n *raft) setObserverLocked(isObserver bool, extSt extensionState) {
|
||||||
// Applies are paused so we're already in observer state.
|
|
||||||
// Resuming the applies will set the state back to whatever
|
|
||||||
// is in "pobserver", so update that instead.
|
|
||||||
n.pobserver = isObserver
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wasObserver := n.observer
|
wasObserver := n.observer
|
||||||
n.observer = isObserver
|
n.observer = isObserver
|
||||||
n.extSt = extSt
|
n.extSt = extSt
|
||||||
@@ -2387,7 +2440,7 @@ func (ae *appendEntry) encode(b []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This can not be used post the wire level callback since we do not copy.
|
// This can not be used post the wire level callback since we do not copy.
|
||||||
func (n *raft) decodeAppendEntry(msg []byte, sub *subscription, reply string) (*appendEntry, error) {
|
func decodeAppendEntry(msg []byte, sub *subscription, reply string) (*appendEntry, error) {
|
||||||
if len(msg) < appendEntryBaseLen {
|
if len(msg) < appendEntryBaseLen {
|
||||||
return nil, errBadAppendEntry
|
return nil, errBadAppendEntry
|
||||||
}
|
}
|
||||||
@@ -2472,7 +2525,7 @@ func (ar *appendEntryResponse) encode(b []byte) []byte {
|
|||||||
// Track all peers we may have ever seen to use an string interns for appendEntryResponse decoding.
|
// Track all peers we may have ever seen to use an string interns for appendEntryResponse decoding.
|
||||||
var peers sync.Map
|
var peers sync.Map
|
||||||
|
|
||||||
func (n *raft) decodeAppendEntryResponse(msg []byte) *appendEntryResponse {
|
func decodeAppendEntryResponse(msg []byte) *appendEntryResponse {
|
||||||
if len(msg) != appendEntryResponseLen {
|
if len(msg) != appendEntryResponseLen {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -2496,16 +2549,18 @@ func (n *raft) decodeAppendEntryResponse(msg []byte) *appendEntryResponse {
|
|||||||
func (n *raft) handleForwardedRemovePeerProposal(sub *subscription, c *client, _ *Account, _, reply string, msg []byte) {
|
func (n *raft) handleForwardedRemovePeerProposal(sub *subscription, c *client, _ *Account, _, reply string, msg []byte) {
|
||||||
n.debug("Received forwarded remove peer proposal: %q", msg)
|
n.debug("Received forwarded remove peer proposal: %q", msg)
|
||||||
|
|
||||||
if n.State() != Leader {
|
|
||||||
n.debug("Ignoring forwarded peer removal proposal, not leader")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(msg) != idLen {
|
if len(msg) != idLen {
|
||||||
n.warn("Received invalid peer name for remove proposal: %q", msg)
|
n.warn("Received invalid peer name for remove proposal: %q", msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
n.RLock()
|
n.RLock()
|
||||||
|
// Check state under lock, we might not be leader anymore.
|
||||||
|
if n.State() != Leader {
|
||||||
|
n.debug("Ignoring forwarded peer removal proposal, not leader")
|
||||||
|
n.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
prop, werr := n.prop, n.werr
|
prop, werr := n.prop, n.werr
|
||||||
n.RUnlock()
|
n.RUnlock()
|
||||||
|
|
||||||
@@ -2521,14 +2576,16 @@ func (n *raft) handleForwardedRemovePeerProposal(sub *subscription, c *client, _
|
|||||||
|
|
||||||
// Called when a peer has forwarded a proposal.
|
// Called when a peer has forwarded a proposal.
|
||||||
func (n *raft) handleForwardedProposal(sub *subscription, c *client, _ *Account, _, reply string, msg []byte) {
|
func (n *raft) handleForwardedProposal(sub *subscription, c *client, _ *Account, _, reply string, msg []byte) {
|
||||||
if n.State() != Leader {
|
|
||||||
n.debug("Ignoring forwarded proposal, not leader")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Need to copy since this is underlying client/route buffer.
|
// Need to copy since this is underlying client/route buffer.
|
||||||
msg = copyBytes(msg)
|
msg = copyBytes(msg)
|
||||||
|
|
||||||
n.RLock()
|
n.RLock()
|
||||||
|
// Check state under lock, we might not be leader anymore.
|
||||||
|
if n.State() != Leader {
|
||||||
|
n.debug("Ignoring forwarded proposal, not leader")
|
||||||
|
n.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
prop, werr := n.prop, n.werr
|
prop, werr := n.prop, n.werr
|
||||||
n.RUnlock()
|
n.RUnlock()
|
||||||
|
|
||||||
@@ -2564,7 +2621,6 @@ func (n *raft) runAsLeader() {
|
|||||||
n.Unlock()
|
n.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
n.Unlock()
|
|
||||||
|
|
||||||
// Cleanup our subscription when we leave.
|
// Cleanup our subscription when we leave.
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -2576,6 +2632,7 @@ func (n *raft) runAsLeader() {
|
|||||||
|
|
||||||
// To send out our initial peer state.
|
// To send out our initial peer state.
|
||||||
n.sendPeerState()
|
n.sendPeerState()
|
||||||
|
n.Unlock()
|
||||||
|
|
||||||
hb := time.NewTicker(hbInterval)
|
hb := time.NewTicker(hbInterval)
|
||||||
defer hb.Stop()
|
defer hb.Stop()
|
||||||
@@ -2728,6 +2785,7 @@ func (n *raft) runCatchup(ar *appendEntryResponse, indexUpdatesQ *ipQueue[uint64
|
|||||||
n.RLock()
|
n.RLock()
|
||||||
s, reply := n.s, n.areply
|
s, reply := n.s, n.areply
|
||||||
peer, subj, term, pterm, last := ar.peer, ar.reply, n.term, n.pterm, n.pindex
|
peer, subj, term, pterm, last := ar.peer, ar.reply, n.term, n.pterm, n.pindex
|
||||||
|
leader := n.State() == Leader // Grab while holding lock, to not race.
|
||||||
n.RUnlock()
|
n.RUnlock()
|
||||||
|
|
||||||
defer s.grWG.Done()
|
defer s.grWG.Done()
|
||||||
@@ -2749,6 +2807,10 @@ func (n *raft) runCatchup(ar *appendEntryResponse, indexUpdatesQ *ipQueue[uint64
|
|||||||
indexUpdatesQ.unregister()
|
indexUpdatesQ.unregister()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if !leader {
|
||||||
|
n.debug("Canceling catchup for %q, not leader anymore", peer)
|
||||||
|
return
|
||||||
|
}
|
||||||
n.debug("Running catchup for %q [%d:%d] to [%d:%d]", peer, ar.term, ar.index, pterm, last)
|
n.debug("Running catchup for %q [%d:%d] to [%d:%d]", peer, ar.term, ar.index, pterm, last)
|
||||||
|
|
||||||
const maxOutstanding = 2 * 1024 * 1024 // 2MB for now.
|
const maxOutstanding = 2 * 1024 * 1024 // 2MB for now.
|
||||||
@@ -2935,7 +2997,7 @@ func (n *raft) loadEntry(index uint64) (*appendEntry, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return n.decodeAppendEntry(sm.msg, nil, _EMPTY_)
|
return decodeAppendEntry(sm.msg, nil, _EMPTY_)
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyCommit will update our commit index and apply the entry to the apply queue.
|
// applyCommit will update our commit index and apply the entry to the apply queue.
|
||||||
@@ -3189,6 +3251,7 @@ func (n *raft) runAsCandidate() {
|
|||||||
votes := map[string]struct{}{
|
votes := map[string]struct{}{
|
||||||
n.ID(): {},
|
n.ID(): {},
|
||||||
}
|
}
|
||||||
|
emptyVotes := map[string]struct{}{}
|
||||||
|
|
||||||
for n.State() == Candidate {
|
for n.State() == Candidate {
|
||||||
elect := n.electTimer()
|
elect := n.electTimer()
|
||||||
@@ -3216,16 +3279,28 @@ func (n *raft) runAsCandidate() {
|
|||||||
}
|
}
|
||||||
n.RLock()
|
n.RLock()
|
||||||
nterm := n.term
|
nterm := n.term
|
||||||
|
csz := n.csz
|
||||||
n.RUnlock()
|
n.RUnlock()
|
||||||
|
|
||||||
if vresp.granted && nterm == vresp.term {
|
if vresp.granted && nterm == vresp.term {
|
||||||
// only track peers that would be our followers
|
// only track peers that would be our followers
|
||||||
n.trackPeer(vresp.peer)
|
n.trackPeer(vresp.peer)
|
||||||
votes[vresp.peer] = struct{}{}
|
if !vresp.empty {
|
||||||
|
votes[vresp.peer] = struct{}{}
|
||||||
|
} else {
|
||||||
|
emptyVotes[vresp.peer] = struct{}{}
|
||||||
|
}
|
||||||
if n.wonElection(len(votes)) {
|
if n.wonElection(len(votes)) {
|
||||||
// Become LEADER if we have won and gotten a quorum with everyone we should hear from.
|
// Become LEADER if we have won and gotten a quorum with everyone we should hear from.
|
||||||
n.switchToLeader()
|
n.switchToLeader()
|
||||||
return
|
return
|
||||||
|
} else if len(votes)+len(emptyVotes) == csz {
|
||||||
|
// Become LEADER if we've got voted in by ALL servers.
|
||||||
|
// We couldn't get quorum based on just our normal votes.
|
||||||
|
// But, we have heard from the full cluster, and some servers came up empty.
|
||||||
|
// We know for sure we have the most up-to-date log.
|
||||||
|
n.switchToLeader()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else if vresp.term > nterm {
|
} else if vresp.term > nterm {
|
||||||
// if we observe a bigger term, we should start over again or risk forming a quorum fully knowing
|
// if we observe a bigger term, we should start over again or risk forming a quorum fully knowing
|
||||||
@@ -3252,7 +3327,7 @@ func (n *raft) runAsCandidate() {
|
|||||||
// is an internal callback from the "asubj" append entry subscription.
|
// is an internal callback from the "asubj" append entry subscription.
|
||||||
func (n *raft) handleAppendEntry(sub *subscription, c *client, _ *Account, _, reply string, msg []byte) {
|
func (n *raft) handleAppendEntry(sub *subscription, c *client, _ *Account, _, reply string, msg []byte) {
|
||||||
msg = copyBytes(msg)
|
msg = copyBytes(msg)
|
||||||
if ae, err := n.decodeAppendEntry(msg, sub, reply); err == nil {
|
if ae, err := decodeAppendEntry(msg, sub, reply); err == nil {
|
||||||
// Push to the new entry channel. From here one of the worker
|
// Push to the new entry channel. From here one of the worker
|
||||||
// goroutines (runAsLeader, runAsFollower, runAsCandidate) will
|
// goroutines (runAsLeader, runAsFollower, runAsCandidate) will
|
||||||
// pick it up.
|
// pick it up.
|
||||||
@@ -3327,10 +3402,13 @@ func (n *raft) truncateWAL(term, index uint64) {
|
|||||||
}
|
}
|
||||||
if index < n.commit {
|
if index < n.commit {
|
||||||
assert.Unreachable("WAL truncate lost commits", map[string]any{
|
assert.Unreachable("WAL truncate lost commits", map[string]any{
|
||||||
"term": term,
|
"n.accName": n.accName,
|
||||||
"index": index,
|
"n.group": n.group,
|
||||||
"commit": n.commit,
|
"n.id": n.id,
|
||||||
"applied": n.applied,
|
"term": term,
|
||||||
|
"index": index,
|
||||||
|
"commit": n.commit,
|
||||||
|
"applied": n.applied,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3345,8 +3423,11 @@ func (n *raft) truncateWAL(term, index uint64) {
|
|||||||
if n.commit > n.pindex {
|
if n.commit > n.pindex {
|
||||||
n.commit = n.pindex
|
n.commit = n.pindex
|
||||||
}
|
}
|
||||||
if n.applied > n.commit {
|
if n.processed > n.commit {
|
||||||
n.applied = n.commit
|
n.processed = n.commit
|
||||||
|
}
|
||||||
|
if n.applied > n.processed {
|
||||||
|
n.applied = n.processed
|
||||||
}
|
}
|
||||||
if n.papplied > n.applied {
|
if n.papplied > n.applied {
|
||||||
n.papplied = n.applied
|
n.papplied = n.applied
|
||||||
@@ -3432,11 +3513,13 @@ func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) {
|
|||||||
assert.Unreachable(
|
assert.Unreachable(
|
||||||
"Two leaders using the same term",
|
"Two leaders using the same term",
|
||||||
map[string]any{
|
map[string]any{
|
||||||
"Node id": n.id,
|
"n.accName": n.accName,
|
||||||
"Node term": n.term,
|
"n.group": n.group,
|
||||||
"AppendEntry id": ae.leader,
|
"n.id": n.id,
|
||||||
"AppendEntry term": ae.term,
|
"n.term": n.term,
|
||||||
"AppendEntry lterm": ae.lterm,
|
"ae.leader": ae.leader,
|
||||||
|
"ae.term": ae.term,
|
||||||
|
"ae.lterm": ae.lterm,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
n.debug("Received append entry from another leader, stepping down to %q", ae.leader)
|
n.debug("Received append entry from another leader, stepping down to %q", ae.leader)
|
||||||
@@ -3660,6 +3743,7 @@ func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) {
|
|||||||
n.Unlock()
|
n.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
n.resetInitializing()
|
||||||
|
|
||||||
// Now send snapshot to upper levels. Only send the snapshot, not the peerstate entry.
|
// Now send snapshot to upper levels. Only send the snapshot, not the peerstate entry.
|
||||||
n.apply.push(newCommittedEntry(n.commit, ae.entries[:1]))
|
n.apply.push(newCommittedEntry(n.commit, ae.entries[:1]))
|
||||||
@@ -3690,6 +3774,7 @@ CONTINUE:
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
n.cachePendingEntry(ae)
|
n.cachePendingEntry(ae)
|
||||||
|
n.resetInitializing()
|
||||||
} else {
|
} else {
|
||||||
// This is a replay on startup so just take the appendEntry version.
|
// This is a replay on startup so just take the appendEntry version.
|
||||||
n.pterm = ae.term
|
n.pterm = ae.term
|
||||||
@@ -3710,7 +3795,7 @@ CONTINUE:
|
|||||||
if !n.observer && !n.paused {
|
if !n.observer && !n.paused {
|
||||||
n.lxfer = true
|
n.lxfer = true
|
||||||
n.xferCampaign()
|
n.xferCampaign()
|
||||||
} else if n.paused && !n.pobserver {
|
} else if n.paused {
|
||||||
// Here we can become a leader but need to wait for resume of the apply queue.
|
// Here we can become a leader but need to wait for resume of the apply queue.
|
||||||
n.lxfer = true
|
n.lxfer = true
|
||||||
}
|
}
|
||||||
@@ -3768,6 +3853,17 @@ CONTINUE:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resetInitializing resets the notion of initializing.
|
||||||
|
// If we were scaling up, also leaves observer mode.
|
||||||
|
// Lock should be held.
|
||||||
|
func (n *raft) resetInitializing() {
|
||||||
|
n.initializing = false
|
||||||
|
if n.scaleUp {
|
||||||
|
n.scaleUp = false
|
||||||
|
n.setObserverLocked(false, extUndetermined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// processPeerState is called when a peer state entry is received
|
// processPeerState is called when a peer state entry is received
|
||||||
// over the wire or when we're updating known peers.
|
// over the wire or when we're updating known peers.
|
||||||
// Lock should be held.
|
// Lock should be held.
|
||||||
@@ -3828,7 +3924,7 @@ func (n *raft) processAppendEntryResponse(ar *appendEntryResponse) {
|
|||||||
|
|
||||||
// handleAppendEntryResponse processes responses to append entries.
|
// handleAppendEntryResponse processes responses to append entries.
|
||||||
func (n *raft) handleAppendEntryResponse(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) {
|
func (n *raft) handleAppendEntryResponse(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) {
|
||||||
ar := n.decodeAppendEntryResponse(msg)
|
ar := decodeAppendEntryResponse(msg)
|
||||||
ar.reply = reply
|
ar.reply = reply
|
||||||
n.resp.push(ar)
|
n.resp.push(ar)
|
||||||
}
|
}
|
||||||
@@ -3892,6 +3988,15 @@ const (
|
|||||||
func (n *raft) sendAppendEntry(entries []*Entry) {
|
func (n *raft) sendAppendEntry(entries []*Entry) {
|
||||||
n.Lock()
|
n.Lock()
|
||||||
defer n.Unlock()
|
defer n.Unlock()
|
||||||
|
n.sendAppendEntryLocked(entries, true)
|
||||||
|
}
|
||||||
|
func (n *raft) sendAppendEntryLocked(entries []*Entry, checkLeader bool) {
|
||||||
|
// Safeguard against sending an append entry right after a stepdown from a different goroutine.
|
||||||
|
// Specifically done while holding the lock to not race.
|
||||||
|
if checkLeader && n.State() != Leader {
|
||||||
|
n.debug("Not sending append entry, not leader")
|
||||||
|
return
|
||||||
|
}
|
||||||
ae := n.buildAppendEntry(entries)
|
ae := n.buildAppendEntry(entries)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@@ -3998,14 +4103,19 @@ func (n *raft) peerNames() []string {
|
|||||||
|
|
||||||
func (n *raft) currentPeerState() *peerState {
|
func (n *raft) currentPeerState() *peerState {
|
||||||
n.RLock()
|
n.RLock()
|
||||||
ps := &peerState{n.peerNames(), n.csz, n.extSt}
|
ps := n.currentPeerStateLocked()
|
||||||
n.RUnlock()
|
n.RUnlock()
|
||||||
return ps
|
return ps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *raft) currentPeerStateLocked() *peerState {
|
||||||
|
return &peerState{n.peerNames(), n.csz, n.extSt}
|
||||||
|
}
|
||||||
|
|
||||||
// sendPeerState will send our current peer state to the cluster.
|
// sendPeerState will send our current peer state to the cluster.
|
||||||
|
// Lock should be held.
|
||||||
func (n *raft) sendPeerState() {
|
func (n *raft) sendPeerState() {
|
||||||
n.sendAppendEntry([]*Entry{{EntryPeerState, encodePeerState(n.currentPeerState())}})
|
n.sendAppendEntryLocked([]*Entry{{EntryPeerState, encodePeerState(n.currentPeerStateLocked())}}, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a heartbeat.
|
// Send a heartbeat.
|
||||||
@@ -4198,6 +4308,7 @@ type voteResponse struct {
|
|||||||
term uint64
|
term uint64
|
||||||
peer string
|
peer string
|
||||||
granted bool
|
granted bool
|
||||||
|
empty bool // "Empty vote", whether this peer's log is empty.
|
||||||
}
|
}
|
||||||
|
|
||||||
const voteResponseLen = 8 + 8 + 1
|
const voteResponseLen = 8 + 8 + 1
|
||||||
@@ -4208,9 +4319,10 @@ func (vr *voteResponse) encode() []byte {
|
|||||||
le.PutUint64(buf[0:], vr.term)
|
le.PutUint64(buf[0:], vr.term)
|
||||||
copy(buf[8:], vr.peer)
|
copy(buf[8:], vr.peer)
|
||||||
if vr.granted {
|
if vr.granted {
|
||||||
buf[16] = 1
|
buf[16] |= 1
|
||||||
} else {
|
}
|
||||||
buf[16] = 0
|
if vr.empty {
|
||||||
|
buf[16] |= 2
|
||||||
}
|
}
|
||||||
return buf[:voteResponseLen]
|
return buf[:voteResponseLen]
|
||||||
}
|
}
|
||||||
@@ -4221,7 +4333,8 @@ func decodeVoteResponse(msg []byte) *voteResponse {
|
|||||||
}
|
}
|
||||||
var le = binary.LittleEndian
|
var le = binary.LittleEndian
|
||||||
vr := &voteResponse{term: le.Uint64(msg[0:]), peer: string(msg[8:16])}
|
vr := &voteResponse{term: le.Uint64(msg[0:]), peer: string(msg[8:16])}
|
||||||
vr.granted = msg[16] == 1
|
vr.granted = msg[16]&1 != 0
|
||||||
|
vr.empty = msg[16]&2 != 0
|
||||||
return vr
|
return vr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4255,7 +4368,7 @@ func (n *raft) processVoteRequest(vr *voteRequest) error {
|
|||||||
|
|
||||||
n.Lock()
|
n.Lock()
|
||||||
|
|
||||||
vresp := &voteResponse{n.term, n.id, false}
|
vresp := &voteResponse{n.term, n.id, false, n.pindex == 0}
|
||||||
defer n.debug("Sending a voteResponse %+v -> %q", vresp, vr.reply)
|
defer n.debug("Sending a voteResponse %+v -> %q", vresp, vr.reply)
|
||||||
|
|
||||||
// Ignore if we are newer. This is important so that we don't accidentally process
|
// Ignore if we are newer. This is important so that we don't accidentally process
|
||||||
@@ -4281,6 +4394,15 @@ func (n *raft) processVoteRequest(vr *voteRequest) error {
|
|||||||
|
|
||||||
// Only way we get to yes is through here.
|
// Only way we get to yes is through here.
|
||||||
voteOk := n.vote == noVote || n.vote == vr.candidate
|
voteOk := n.vote == noVote || n.vote == vr.candidate
|
||||||
|
|
||||||
|
// If we have an empty log, but are initializing.
|
||||||
|
if voteOk && vresp.empty && n.initializing {
|
||||||
|
// Reset notion of having an empty log if we're voting during initialization/scale up.
|
||||||
|
// Ensures they only need quorum, and not need to hear from all servers.
|
||||||
|
vresp.empty = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other server's log needs to be equal or more up-to-date than ours.
|
||||||
if voteOk && (vr.lastTerm > n.pterm || vr.lastTerm == n.pterm && vr.lastIndex >= n.pindex) {
|
if voteOk && (vr.lastTerm > n.pterm || vr.lastTerm == n.pterm && vr.lastIndex >= n.pindex) {
|
||||||
vresp.granted = true
|
vresp.granted = true
|
||||||
n.term = vr.term
|
n.term = vr.term
|
||||||
@@ -4450,7 +4572,7 @@ func (n *raft) switchToCandidate() {
|
|||||||
|
|
||||||
// If we are catching up or are in observer mode we can not switch.
|
// If we are catching up or are in observer mode we can not switch.
|
||||||
// Avoid petitioning to become leader if we're behind on applies.
|
// Avoid petitioning to become leader if we're behind on applies.
|
||||||
if n.observer || n.paused || n.applied < n.commit {
|
if n.observer || n.paused || n.processed < n.commit {
|
||||||
n.resetElect(minElectionTimeout / 4)
|
n.resetElect(minElectionTimeout / 4)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+123
-10
@@ -369,6 +369,19 @@ func (u *tagsOption) IsStatszChange() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// metadataOption implements the option interface for the `metadata` setting.
|
||||||
|
type metadataOption struct {
|
||||||
|
noopOption // Not authOption because this is a no-op; will be reloaded with options.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *metadataOption) Apply(server *Server) {
|
||||||
|
server.Noticef("Reloaded: metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *metadataOption) IsStatszChange() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// usersOption implements the option interface for the authorization `users`
|
// usersOption implements the option interface for the authorization `users`
|
||||||
// setting.
|
// setting.
|
||||||
type usersOption struct {
|
type usersOption struct {
|
||||||
@@ -862,6 +875,7 @@ type leafNodeOption struct {
|
|||||||
noopOption
|
noopOption
|
||||||
tlsFirstChanged bool
|
tlsFirstChanged bool
|
||||||
compressionChanged bool
|
compressionChanged bool
|
||||||
|
disabledChanged bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *leafNodeOption) Apply(s *Server) {
|
func (l *leafNodeOption) Apply(s *Server) {
|
||||||
@@ -873,8 +887,9 @@ func (l *leafNodeOption) Apply(s *Server) {
|
|||||||
s.Noticef("Reloaded: LeafNode Remote to %v TLS HandshakeFirst value is: %v", r.URLs, r.TLSHandshakeFirst)
|
s.Noticef("Reloaded: LeafNode Remote to %v TLS HandshakeFirst value is: %v", r.URLs, r.TLSHandshakeFirst)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if l.compressionChanged {
|
if l.compressionChanged || l.disabledChanged {
|
||||||
var leafs []*client
|
var leafs []*client
|
||||||
|
var solicit []*leafNodeCfg
|
||||||
acceptSideCompOpts := &opts.LeafNode.Compression
|
acceptSideCompOpts := &opts.LeafNode.Compression
|
||||||
|
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
@@ -887,10 +902,15 @@ func (l *leafNodeOption) Apply(s *Server) {
|
|||||||
if l := len(s.leafRemoteCfgs); l < max {
|
if l := len(s.leafRemoteCfgs); l < max {
|
||||||
max = l
|
max = l
|
||||||
}
|
}
|
||||||
for i := 0; i < max; i++ {
|
for i := range max {
|
||||||
lr := s.leafRemoteCfgs[i]
|
lr := s.leafRemoteCfgs[i]
|
||||||
|
or := opts.LeafNode.Remotes[i]
|
||||||
lr.Lock()
|
lr.Lock()
|
||||||
lr.Compression = opts.LeafNode.Remotes[i].Compression
|
lr.Compression = or.Compression
|
||||||
|
if lr.Disabled && !or.Disabled {
|
||||||
|
solicit = append(solicit, lr)
|
||||||
|
}
|
||||||
|
lr.Disabled = or.Disabled
|
||||||
lr.Unlock()
|
lr.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -899,6 +919,13 @@ func (l *leafNodeOption) Apply(s *Server) {
|
|||||||
|
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
if r := l.leaf.remote; r != nil {
|
if r := l.leaf.remote; r != nil {
|
||||||
|
// If newly marked as disabled, collect and ignore the rest.
|
||||||
|
if r.Disabled {
|
||||||
|
l.flags.set(noReconnect)
|
||||||
|
leafs = append(leafs, l)
|
||||||
|
l.mu.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
co = &r.Compression
|
co = &r.Compression
|
||||||
} else {
|
} else {
|
||||||
co = acceptSideCompOpts
|
co = acceptSideCompOpts
|
||||||
@@ -929,11 +956,25 @@ func (l *leafNodeOption) Apply(s *Server) {
|
|||||||
l.mu.Unlock()
|
l.mu.Unlock()
|
||||||
}
|
}
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
// Close the connections for which negotiation is required.
|
// Close the connections for which negotiation is required, or that
|
||||||
|
// have been disabled.
|
||||||
for _, l := range leafs {
|
for _, l := range leafs {
|
||||||
l.closeConnection(ClientClosed)
|
l.closeConnection(ClientClosed)
|
||||||
}
|
}
|
||||||
s.Noticef("Reloaded: LeafNode compression settings")
|
if l.compressionChanged {
|
||||||
|
s.Noticef("Reloaded: LeafNode compression settings")
|
||||||
|
}
|
||||||
|
if l.disabledChanged {
|
||||||
|
if len(leafs) > 0 {
|
||||||
|
s.Noticef("Reloaded: LeafNode(s) disabled")
|
||||||
|
}
|
||||||
|
if len(solicit) > 0 {
|
||||||
|
for _, remote := range solicit {
|
||||||
|
s.startGoRoutine(func() { s.connectToRemoteLeafNode(remote, true) })
|
||||||
|
}
|
||||||
|
s.Noticef("Reloaded: LeafNode(s) enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1011,6 +1052,38 @@ func (s *Server) recheckPinnedCerts(curOpts *Options, newOpts *Options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type proxiesReload struct {
|
||||||
|
noopOption
|
||||||
|
add []string
|
||||||
|
del []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *proxiesReload) Apply(s *Server) {
|
||||||
|
var clients []*client
|
||||||
|
s.mu.Lock()
|
||||||
|
for _, k := range p.del {
|
||||||
|
cc := s.proxiedConns[k]
|
||||||
|
delete(s.proxiedConns, k)
|
||||||
|
if len(cc) > 0 {
|
||||||
|
for _, c := range cc {
|
||||||
|
clients = append(clients, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.processProxiesTrustedKeys()
|
||||||
|
s.mu.Unlock()
|
||||||
|
if len(p.del) > 0 {
|
||||||
|
for _, c := range clients {
|
||||||
|
c.setAuthError(ErrAuthProxyNotTrusted)
|
||||||
|
c.authViolation()
|
||||||
|
}
|
||||||
|
s.Noticef("Reloaded: proxies trusted keys %q were removed", p.add)
|
||||||
|
}
|
||||||
|
if len(p.add) > 0 {
|
||||||
|
s.Noticef("Reloaded: proxies trusted keys %q were added", p.add)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reload reads the current configuration file and calls out to ReloadOptions
|
// Reload reads the current configuration file and calls out to ReloadOptions
|
||||||
// to apply the changes. This returns an error if the server was not started
|
// to apply the changes. This returns an error if the server was not started
|
||||||
// with a config file or an option which doesn't support hot-swapping was changed.
|
// with a config file or an option which doesn't support hot-swapping was changed.
|
||||||
@@ -1186,7 +1259,7 @@ func imposeOrder(value any) error {
|
|||||||
slices.Sort(value.AllowedOrigins)
|
slices.Sort(value.AllowedOrigins)
|
||||||
case string, bool, uint8, uint16, int, int32, int64, time.Duration, float64, nil, LeafNodeOpts, ClusterOpts, *tls.Config, PinnedCertSet,
|
case string, bool, uint8, uint16, int, int32, int64, time.Duration, float64, nil, LeafNodeOpts, ClusterOpts, *tls.Config, PinnedCertSet,
|
||||||
*URLAccResolver, *MemAccResolver, *DirAccResolver, *CacheDirAccResolver, Authentication, MQTTOpts, jwt.TagList,
|
*URLAccResolver, *MemAccResolver, *DirAccResolver, *CacheDirAccResolver, Authentication, MQTTOpts, jwt.TagList,
|
||||||
*OCSPConfig, map[string]string, JSLimitOpts, StoreCipher, *OCSPResponseCacheConfig:
|
*OCSPConfig, map[string]string, JSLimitOpts, StoreCipher, *OCSPResponseCacheConfig, *ProxiesConfig:
|
||||||
// explicitly skipped types
|
// explicitly skipped types
|
||||||
case *AuthCallout:
|
case *AuthCallout:
|
||||||
case JSTpmOpts:
|
case JSTpmOpts:
|
||||||
@@ -1283,6 +1356,8 @@ func (s *Server) diffOptions(newOpts *Options) ([]option, error) {
|
|||||||
diffOpts = append(diffOpts, &passwordOption{})
|
diffOpts = append(diffOpts, &passwordOption{})
|
||||||
case "tags":
|
case "tags":
|
||||||
diffOpts = append(diffOpts, &tagsOption{})
|
diffOpts = append(diffOpts, &tagsOption{})
|
||||||
|
case "metadata":
|
||||||
|
diffOpts = append(diffOpts, &metadataOption{})
|
||||||
case "authorization":
|
case "authorization":
|
||||||
diffOpts = append(diffOpts, &authorizationOption{})
|
diffOpts = append(diffOpts, &authorizationOption{})
|
||||||
case "authtimeout":
|
case "authtimeout":
|
||||||
@@ -1300,7 +1375,7 @@ func (s *Server) diffOptions(newOpts *Options) ([]option, error) {
|
|||||||
co := &clusterOption{
|
co := &clusterOption{
|
||||||
newValue: newClusterOpts,
|
newValue: newClusterOpts,
|
||||||
permsChanged: !reflect.DeepEqual(newClusterOpts.Permissions, oldClusterOpts.Permissions),
|
permsChanged: !reflect.DeepEqual(newClusterOpts.Permissions, oldClusterOpts.Permissions),
|
||||||
compressChanged: !reflect.DeepEqual(oldClusterOpts.Compression, newClusterOpts.Compression),
|
compressChanged: !compressOptsEqual(&oldClusterOpts.Compression, &newClusterOpts.Compression),
|
||||||
}
|
}
|
||||||
co.diffPoolAndAccounts(&oldClusterOpts)
|
co.diffPoolAndAccounts(&oldClusterOpts)
|
||||||
// If there are added accounts, first make sure that we can look them up.
|
// If there are added accounts, first make sure that we can look them up.
|
||||||
@@ -1413,18 +1488,26 @@ func (s *Server) diffOptions(newOpts *Options) ([]option, error) {
|
|||||||
}
|
}
|
||||||
// We also support config reload for compression. Check if it changed before
|
// We also support config reload for compression. Check if it changed before
|
||||||
// blanking them out for the deep-equal check at the end.
|
// blanking them out for the deep-equal check at the end.
|
||||||
compressionChanged := !reflect.DeepEqual(tmpOld.Compression, tmpNew.Compression)
|
compressionChanged := !compressOptsEqual(&tmpOld.Compression, &tmpNew.Compression)
|
||||||
if compressionChanged {
|
if compressionChanged {
|
||||||
tmpOld.Compression, tmpNew.Compression = CompressionOpts{}, CompressionOpts{}
|
tmpOld.Compression, tmpNew.Compression = CompressionOpts{}, CompressionOpts{}
|
||||||
} else if len(tmpOld.Remotes) == len(tmpNew.Remotes) {
|
} else if len(tmpOld.Remotes) == len(tmpNew.Remotes) {
|
||||||
// Same that for tls first check, do the remotes now.
|
// Same that for tls first check, do the remotes now.
|
||||||
for i := 0; i < len(tmpOld.Remotes); i++ {
|
for i := range len(tmpOld.Remotes) {
|
||||||
if !reflect.DeepEqual(tmpOld.Remotes[i].Compression, tmpNew.Remotes[i].Compression) {
|
if !compressOptsEqual(&tmpOld.Remotes[i].Compression, &tmpNew.Remotes[i].Compression) {
|
||||||
compressionChanged = true
|
compressionChanged = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Check if the "disabled" option of each remote has changed.
|
||||||
|
var disabledChanged bool
|
||||||
|
for i := range len(tmpOld.Remotes) {
|
||||||
|
if tmpOld.Remotes[i].Disabled != tmpNew.Remotes[i].Disabled {
|
||||||
|
disabledChanged = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Need to do the same for remote leafnodes' TLS configs.
|
// Need to do the same for remote leafnodes' TLS configs.
|
||||||
// But we can't just set remotes' TLSConfig to nil otherwise this
|
// But we can't just set remotes' TLSConfig to nil otherwise this
|
||||||
@@ -1517,6 +1600,7 @@ func (s *Server) diffOptions(newOpts *Options) ([]option, error) {
|
|||||||
diffOpts = append(diffOpts, &leafNodeOption{
|
diffOpts = append(diffOpts, &leafNodeOption{
|
||||||
tlsFirstChanged: handshakeFirstChanged,
|
tlsFirstChanged: handshakeFirstChanged,
|
||||||
compressionChanged: compressionChanged,
|
compressionChanged: compressionChanged,
|
||||||
|
disabledChanged: disabledChanged,
|
||||||
})
|
})
|
||||||
case "jetstream":
|
case "jetstream":
|
||||||
new := newValue.(bool)
|
new := newValue.(bool)
|
||||||
@@ -1670,6 +1754,12 @@ func (s *Server) diffOptions(newOpts *Options) ([]option, error) {
|
|||||||
continue
|
continue
|
||||||
case "nofastproducerstall":
|
case "nofastproducerstall":
|
||||||
diffOpts = append(diffOpts, &noFastProdStallReload{noStall: newValue.(bool)})
|
diffOpts = append(diffOpts, &noFastProdStallReload{noStall: newValue.(bool)})
|
||||||
|
case "proxies":
|
||||||
|
new := newValue.(*ProxiesConfig)
|
||||||
|
old := oldValue.(*ProxiesConfig)
|
||||||
|
if add, del := diffProxiesTrustedKeys(old.Trusted, new.Trusted); len(add) > 0 || len(del) > 0 {
|
||||||
|
diffOpts = append(diffOpts, &proxiesReload{add: add, del: del})
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// TODO(ik): Implement String() on those options to have a nice print.
|
// TODO(ik): Implement String() on those options to have a nice print.
|
||||||
// %v is difficult to figure what's what, %+v print private fields and
|
// %v is difficult to figure what's what, %+v print private fields and
|
||||||
@@ -1729,6 +1819,8 @@ func copyRemoteLNConfigForReloadCompare(current []*RemoteLeafOpts) []*RemoteLeaf
|
|||||||
cp.DenyImports, cp.DenyExports = nil, nil
|
cp.DenyImports, cp.DenyExports = nil, nil
|
||||||
// Remove compression mode
|
// Remove compression mode
|
||||||
cp.Compression = CompressionOpts{}
|
cp.Compression = CompressionOpts{}
|
||||||
|
// Reset disabled status
|
||||||
|
cp.Disabled = false
|
||||||
rlns = append(rlns, &cp)
|
rlns = append(rlns, &cp)
|
||||||
}
|
}
|
||||||
return rlns
|
return rlns
|
||||||
@@ -2535,3 +2627,24 @@ addLoop:
|
|||||||
|
|
||||||
return add, remove
|
return add, remove
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func diffProxiesTrustedKeys(old, new []*ProxyConfig) ([]string, []string) {
|
||||||
|
var add []string
|
||||||
|
var del []string
|
||||||
|
// Both "old" and "new" lists should be small...
|
||||||
|
for _, op := range old {
|
||||||
|
if !slices.ContainsFunc(new, func(pc *ProxyConfig) bool {
|
||||||
|
return pc.Key == op.Key
|
||||||
|
}) {
|
||||||
|
del = append(del, op.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, np := range new {
|
||||||
|
if !slices.ContainsFunc(old, func(pc *ProxyConfig) bool {
|
||||||
|
return pc.Key == np.Key
|
||||||
|
}) {
|
||||||
|
add = append(add, np.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return add, del
|
||||||
|
}
|
||||||
|
|||||||
+11
-2
@@ -143,6 +143,7 @@ const (
|
|||||||
// Can be changed for tests
|
// Can be changed for tests
|
||||||
var (
|
var (
|
||||||
routeConnectDelay = DEFAULT_ROUTE_CONNECT
|
routeConnectDelay = DEFAULT_ROUTE_CONNECT
|
||||||
|
routeConnectMaxDelay = DEFAULT_ROUTE_CONNECT_MAX
|
||||||
routeMaxPingInterval = defaultRouteMaxPingInterval
|
routeMaxPingInterval = defaultRouteMaxPingInterval
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2054,7 +2055,7 @@ func (s *Server) createRoute(conn net.Conn, rURL *url.URL, rtype RouteType, goss
|
|||||||
if tlsRequired {
|
if tlsRequired {
|
||||||
c.Debugf("TLS handshake complete")
|
c.Debugf("TLS handshake complete")
|
||||||
cs := c.nc.(*tls.Conn).ConnectionState()
|
cs := c.nc.(*tls.Conn).ConnectionState()
|
||||||
c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tlsCipher(cs.CipherSuite))
|
c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tls.CipherSuiteName(cs.CipherSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue Connect proto if we solicited the connection.
|
// Queue Connect proto if we solicited the connection.
|
||||||
@@ -2893,6 +2894,7 @@ func (s *Server) connectToRoute(rURL *url.URL, rtype RouteType, firstConnect boo
|
|||||||
excludedAddresses := s.routesToSelf
|
excludedAddresses := s.routesToSelf
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
|
|
||||||
|
attemptDelay := routeConnectDelay
|
||||||
for attempts := 0; s.isRunning(); {
|
for attempts := 0; s.isRunning(); {
|
||||||
if tryForEver {
|
if tryForEver {
|
||||||
if !s.routeStillValid(rURL) {
|
if !s.routeStillValid(rURL) {
|
||||||
@@ -2935,7 +2937,14 @@ func (s *Server) connectToRoute(rURL *url.URL, rtype RouteType, firstConnect boo
|
|||||||
select {
|
select {
|
||||||
case <-s.quitCh:
|
case <-s.quitCh:
|
||||||
return
|
return
|
||||||
case <-time.After(routeConnectDelay):
|
case <-time.After(attemptDelay):
|
||||||
|
if opts.Cluster.ConnectBackoff {
|
||||||
|
// Use exponential backoff for connection attempts.
|
||||||
|
attemptDelay *= 2
|
||||||
|
if attemptDelay > routeConnectMaxDelay {
|
||||||
|
attemptDelay = routeConnectMaxDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+239
@@ -0,0 +1,239 @@
|
|||||||
|
// Copyright 2025 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nats-io/nats-server/v2/server/thw"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error for when we try to decode a binary-encoded message schedule with an unknown version number.
|
||||||
|
var ErrMsgScheduleInvalidVersion = errors.New("msg scheduling: encoded version not known")
|
||||||
|
|
||||||
|
const (
|
||||||
|
headerLen = 17 // 1 byte magic + 2x uint64s
|
||||||
|
)
|
||||||
|
|
||||||
|
type MsgScheduling struct {
|
||||||
|
run func()
|
||||||
|
ttls *thw.HashWheel
|
||||||
|
timer *time.Timer
|
||||||
|
schedules map[string]*MsgSchedule
|
||||||
|
seqToSubj map[uint64]string
|
||||||
|
inflight map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MsgSchedule struct {
|
||||||
|
seq uint64
|
||||||
|
ts int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMsgScheduling(run func()) *MsgScheduling {
|
||||||
|
return &MsgScheduling{
|
||||||
|
run: run,
|
||||||
|
ttls: thw.NewHashWheel(),
|
||||||
|
schedules: make(map[string]*MsgSchedule),
|
||||||
|
seqToSubj: make(map[uint64]string),
|
||||||
|
inflight: make(map[string]struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MsgScheduling) add(seq uint64, subj string, ts int64) {
|
||||||
|
ms.init(seq, subj, ts)
|
||||||
|
ms.resetTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MsgScheduling) init(seq uint64, subj string, ts int64) {
|
||||||
|
if sched, ok := ms.schedules[subj]; ok {
|
||||||
|
delete(ms.seqToSubj, sched.seq)
|
||||||
|
// Remove and add separately, since they'll have different sequences.
|
||||||
|
ms.ttls.Remove(sched.seq, sched.ts)
|
||||||
|
ms.ttls.Add(seq, ts)
|
||||||
|
sched.ts, sched.seq = ts, seq
|
||||||
|
} else {
|
||||||
|
ms.ttls.Add(seq, ts)
|
||||||
|
ms.schedules[subj] = &MsgSchedule{seq: seq, ts: ts}
|
||||||
|
}
|
||||||
|
ms.seqToSubj[seq] = subj
|
||||||
|
delete(ms.inflight, subj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MsgScheduling) markInflight(subj string) {
|
||||||
|
if _, ok := ms.schedules[subj]; ok {
|
||||||
|
ms.inflight[subj] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MsgScheduling) isInflight(subj string) bool {
|
||||||
|
_, ok := ms.inflight[subj]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MsgScheduling) remove(seq uint64) {
|
||||||
|
if subj, ok := ms.seqToSubj[seq]; ok {
|
||||||
|
delete(ms.seqToSubj, seq)
|
||||||
|
delete(ms.schedules, subj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MsgScheduling) clearInflight() {
|
||||||
|
ms.inflight = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MsgScheduling) resetTimer() {
|
||||||
|
next := ms.ttls.GetNextExpiration(math.MaxInt64)
|
||||||
|
if next == math.MaxInt64 {
|
||||||
|
clearTimer(&ms.timer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fireIn := time.Until(time.Unix(0, next))
|
||||||
|
|
||||||
|
// Make sure we aren't firing too often either way, otherwise we can
|
||||||
|
// negatively impact stream ingest performance.
|
||||||
|
if fireIn < 250*time.Millisecond {
|
||||||
|
fireIn = 250 * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
if ms.timer != nil {
|
||||||
|
ms.timer.Reset(fireIn)
|
||||||
|
} else {
|
||||||
|
ms.timer = time.AfterFunc(fireIn, ms.run)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MsgScheduling) getScheduledMessages(loadMsg func(seq uint64, smv *StoreMsg) *StoreMsg) []*inMsg {
|
||||||
|
var (
|
||||||
|
smv StoreMsg
|
||||||
|
sm *StoreMsg
|
||||||
|
msgs []*inMsg
|
||||||
|
)
|
||||||
|
ms.ttls.ExpireTasks(func(seq uint64, ts int64) bool {
|
||||||
|
// Need to grab the message for the specified sequence, and check
|
||||||
|
// if it hasn't been removed in the meantime.
|
||||||
|
sm = loadMsg(seq, &smv)
|
||||||
|
if sm != nil {
|
||||||
|
// If already inflight, don't duplicate a scheduled message. The stream could
|
||||||
|
// be replicated and the scheduled message could take some time to propagate.
|
||||||
|
if ms.isInflight(sm.subj) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Validate the contents are correct if not, we just remove it from THW.
|
||||||
|
ttl, ok := getMessageScheduleTTL(sm.hdr)
|
||||||
|
if !ok {
|
||||||
|
ms.remove(seq)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
target := getMessageScheduleTarget(sm.hdr)
|
||||||
|
if target == _EMPTY_ {
|
||||||
|
ms.remove(seq)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy, as this is retrieved directly from storage, and we'll need to keep hold of this for some time.
|
||||||
|
// And in the case of headers, we'll copy all of them, but make changes.
|
||||||
|
hdr, msg := copyBytes(sm.hdr), copyBytes(sm.msg)
|
||||||
|
|
||||||
|
// Strip headers specific to the schedule.
|
||||||
|
hdr = removeHeaderIfPresent(hdr, JSSchedulePattern)
|
||||||
|
hdr = removeHeaderIfPrefixPresent(hdr, "Nats-Schedule-")
|
||||||
|
hdr = removeHeaderIfPrefixPresent(hdr, "Nats-Expected-")
|
||||||
|
hdr = removeHeaderIfPresent(hdr, JSMsgId)
|
||||||
|
hdr = removeHeaderIfPresent(hdr, JSMessageTTL)
|
||||||
|
hdr = removeHeaderIfPresent(hdr, JSMsgRollup)
|
||||||
|
|
||||||
|
// Add headers for the scheduled message.
|
||||||
|
hdr = genHeader(hdr, JSScheduler, sm.subj)
|
||||||
|
hdr = genHeader(hdr, JSScheduleNext, JSScheduleNextPurge) // Purge the schedule message itself.
|
||||||
|
if ttl != _EMPTY_ {
|
||||||
|
hdr = genHeader(hdr, JSMessageTTL, ttl)
|
||||||
|
}
|
||||||
|
msgs = append(msgs, &inMsg{seq: seq, subj: target, hdr: hdr, msg: msg})
|
||||||
|
ms.markInflight(sm.subj)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ms.remove(seq)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
// THW is unordered, so must sort by sequence.
|
||||||
|
slices.SortFunc(msgs, func(a, b *inMsg) int {
|
||||||
|
if a.seq == b.seq {
|
||||||
|
return 0
|
||||||
|
} else if a.seq < b.seq {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return msgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode writes out the contents of the schedule into a binary snapshot
|
||||||
|
// and returns it. The high seq number is included in the snapshot and will
|
||||||
|
// be returned on decode.
|
||||||
|
func (ms *MsgScheduling) encode(highSeq uint64) []byte {
|
||||||
|
count := uint64(len(ms.schedules))
|
||||||
|
b := make([]byte, 0, headerLen+(count*(2*binary.MaxVarintLen64)))
|
||||||
|
b = append(b, 1) // Magic version
|
||||||
|
b = binary.LittleEndian.AppendUint64(b, count) // Entry count
|
||||||
|
b = binary.LittleEndian.AppendUint64(b, highSeq) // Stamp
|
||||||
|
for subj, sched := range ms.schedules {
|
||||||
|
slen := min(uint64(len(subj)), math.MaxUint16)
|
||||||
|
b = binary.LittleEndian.AppendUint16(b, uint16(slen))
|
||||||
|
b = append(b, subj[:slen]...)
|
||||||
|
b = binary.AppendVarint(b, sched.ts)
|
||||||
|
b = binary.AppendUvarint(b, sched.seq)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode snapshots a binary-encoded schedule and replaces the contents of this
|
||||||
|
// schedule with them. Returns the high seq number from the snapshot.
|
||||||
|
func (ms *MsgScheduling) decode(b []byte) (uint64, error) {
|
||||||
|
if len(b) < headerLen {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
if b[0] != 1 {
|
||||||
|
return 0, ErrMsgScheduleInvalidVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
count := binary.LittleEndian.Uint64(b[1:])
|
||||||
|
stamp := binary.LittleEndian.Uint64(b[9:])
|
||||||
|
b = b[headerLen:]
|
||||||
|
for i := uint64(0); i < count; i++ {
|
||||||
|
sl := int(binary.LittleEndian.Uint16(b))
|
||||||
|
b = b[2:]
|
||||||
|
if len(b) < sl {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
subj := string(b[:sl])
|
||||||
|
b = b[sl:]
|
||||||
|
ts, tn := binary.Varint(b)
|
||||||
|
if tn < 0 {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
seq, vn := binary.Uvarint(b[tn:])
|
||||||
|
if vn < 0 {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
ms.init(seq, subj, ts)
|
||||||
|
b = b[tn+vn:]
|
||||||
|
}
|
||||||
|
return stamp, nil
|
||||||
|
}
|
||||||
+111
-23
@@ -30,6 +30,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
@@ -129,6 +130,10 @@ type Info struct {
|
|||||||
WSConnectURLs []string `json:"ws_connect_urls,omitempty"` // Contains URLs a ws client can connect to.
|
WSConnectURLs []string `json:"ws_connect_urls,omitempty"` // Contains URLs a ws client can connect to.
|
||||||
LameDuckMode bool `json:"ldm,omitempty"`
|
LameDuckMode bool `json:"ldm,omitempty"`
|
||||||
Compression string `json:"compression,omitempty"`
|
Compression string `json:"compression,omitempty"`
|
||||||
|
ConnectInfo bool `json:"connect_info,omitempty"` // When true this is the server INFO response to CONNECT
|
||||||
|
RemoteAccount string `json:"remote_account,omitempty"` // Lets the client or leafnode side know the remote account that they bind to.
|
||||||
|
IsSystemAccount bool `json:"acc_is_sys,omitempty"` // Indicates if the account is a system account.
|
||||||
|
JSApiLevel int `json:"api_lvl,omitempty"`
|
||||||
|
|
||||||
// Route Specific
|
// Route Specific
|
||||||
Import *SubjectPermission `json:"import,omitempty"`
|
Import *SubjectPermission `json:"import,omitempty"`
|
||||||
@@ -136,7 +141,6 @@ type Info struct {
|
|||||||
LNOC bool `json:"lnoc,omitempty"`
|
LNOC bool `json:"lnoc,omitempty"`
|
||||||
LNOCU bool `json:"lnocu,omitempty"`
|
LNOCU bool `json:"lnocu,omitempty"`
|
||||||
InfoOnConnect bool `json:"info_on_connect,omitempty"` // When true the server will respond to CONNECT with an INFO
|
InfoOnConnect bool `json:"info_on_connect,omitempty"` // When true the server will respond to CONNECT with an INFO
|
||||||
ConnectInfo bool `json:"connect_info,omitempty"` // When true this is the server INFO response to CONNECT
|
|
||||||
RoutePoolSize int `json:"route_pool_size,omitempty"`
|
RoutePoolSize int `json:"route_pool_size,omitempty"`
|
||||||
RoutePoolIdx int `json:"route_pool_idx,omitempty"`
|
RoutePoolIdx int `json:"route_pool_idx,omitempty"`
|
||||||
RouteAccount string `json:"route_account,omitempty"`
|
RouteAccount string `json:"route_account,omitempty"`
|
||||||
@@ -153,8 +157,7 @@ type Info struct {
|
|||||||
GatewayIOM bool `json:"gateway_iom,omitempty"` // Indicate that all accounts will be switched to InterestOnly mode "right away"
|
GatewayIOM bool `json:"gateway_iom,omitempty"` // Indicate that all accounts will be switched to InterestOnly mode "right away"
|
||||||
|
|
||||||
// LeafNode Specific
|
// LeafNode Specific
|
||||||
LeafNodeURLs []string `json:"leafnode_urls,omitempty"` // LeafNode URLs that the server can reconnect to.
|
LeafNodeURLs []string `json:"leafnode_urls,omitempty"` // LeafNode URLs that the server can reconnect to.
|
||||||
RemoteAccount string `json:"remote_account,omitempty"` // Lets the other side know the remote account that they bind to.
|
|
||||||
|
|
||||||
XKey string `json:"xkey,omitempty"` // Public server's x25519 key.
|
XKey string `json:"xkey,omitempty"` // Public server's x25519 key.
|
||||||
}
|
}
|
||||||
@@ -167,6 +170,7 @@ type Server struct {
|
|||||||
pinnedAccFail uint64
|
pinnedAccFail uint64
|
||||||
stats
|
stats
|
||||||
scStats
|
scStats
|
||||||
|
staleStats
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
reloadMu sync.RWMutex // Write-locked when a config reload is taking place ONLY
|
reloadMu sync.RWMutex // Write-locked when a config reload is taking place ONLY
|
||||||
kp nkeys.KeyPair
|
kp nkeys.KeyPair
|
||||||
@@ -259,8 +263,6 @@ type Server struct {
|
|||||||
// Used internally for quick look-ups.
|
// Used internally for quick look-ups.
|
||||||
clientConnectURLsMap refCountedUrlSet
|
clientConnectURLsMap refCountedUrlSet
|
||||||
|
|
||||||
lastCURLsUpdate int64
|
|
||||||
|
|
||||||
// For Gateways
|
// For Gateways
|
||||||
gatewayListener net.Listener // Accept listener
|
gatewayListener net.Listener // Accept listener
|
||||||
gatewayListenerErr error
|
gatewayListenerErr error
|
||||||
@@ -371,6 +373,11 @@ type Server struct {
|
|||||||
// Controls whether or not the account NRG capability is set in statsz.
|
// Controls whether or not the account NRG capability is set in statsz.
|
||||||
// Currently used by unit tests to simulate nodes not supporting account NRG.
|
// Currently used by unit tests to simulate nodes not supporting account NRG.
|
||||||
accountNRGAllowed atomic.Bool
|
accountNRGAllowed atomic.Bool
|
||||||
|
|
||||||
|
// List of proxies trusted keys in `KeyPair` form so we can do signature
|
||||||
|
// verification when processing incoming proxy connections.
|
||||||
|
proxiesKeyPairs []nkeys.KeyPair
|
||||||
|
proxiedConns map[string]map[uint64]*client
|
||||||
}
|
}
|
||||||
|
|
||||||
// For tracking JS nodes.
|
// For tracking JS nodes.
|
||||||
@@ -390,11 +397,13 @@ type nodeInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type stats struct {
|
type stats struct {
|
||||||
inMsgs int64
|
inMsgs int64
|
||||||
outMsgs int64
|
outMsgs int64
|
||||||
inBytes int64
|
inBytes int64
|
||||||
outBytes int64
|
outBytes int64
|
||||||
slowConsumers int64
|
slowConsumers int64
|
||||||
|
staleConnections int64
|
||||||
|
stalls int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// scStats includes the total and per connection counters of Slow Consumers.
|
// scStats includes the total and per connection counters of Slow Consumers.
|
||||||
@@ -405,6 +414,14 @@ type scStats struct {
|
|||||||
gateways atomic.Uint64
|
gateways atomic.Uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// staleStats includes the total and per connection counters of Stale Connections.
|
||||||
|
type staleStats struct {
|
||||||
|
clients atomic.Uint64
|
||||||
|
routes atomic.Uint64
|
||||||
|
leafs atomic.Uint64
|
||||||
|
gateways atomic.Uint64
|
||||||
|
}
|
||||||
|
|
||||||
// This is used by tests so we can run all server tests with a default route
|
// This is used by tests so we can run all server tests with a default route
|
||||||
// or leafnode compression mode. For instance:
|
// or leafnode compression mode. For instance:
|
||||||
// go test -race -v ./server -cluster_compression=fast
|
// go test -race -v ./server -cluster_compression=fast
|
||||||
@@ -625,6 +642,32 @@ func selectS2AutoModeBasedOnRTT(rtt time.Duration, rttThresholds []time.Duration
|
|||||||
return CompressionS2Best
|
return CompressionS2Best
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func compressOptsEqual(c1, c2 *CompressionOpts) bool {
|
||||||
|
if c1 == c2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (c1 == nil && c2 != nil) || (c1 != nil && c2 == nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c1.Mode != c2.Mode {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// For s2_auto, if one has an empty RTTThresholds, it is equivalent
|
||||||
|
// to the defaultCompressionS2AutoRTTThresholds array, so compare with that.
|
||||||
|
if c1.Mode == CompressionS2Auto {
|
||||||
|
if len(c1.RTTThresholds) == 0 && !reflect.DeepEqual(c2.RTTThresholds, defaultCompressionS2AutoRTTThresholds) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(c2.RTTThresholds) == 0 && !reflect.DeepEqual(c1.RTTThresholds, defaultCompressionS2AutoRTTThresholds) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(c1.RTTThresholds, c2.RTTThresholds) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Returns an array of s2 WriterOption based on the route compression mode.
|
// Returns an array of s2 WriterOption based on the route compression mode.
|
||||||
// So far we return a single option, but this way we can call s2.NewWriter()
|
// So far we return a single option, but this way we can call s2.NewWriter()
|
||||||
// with a nil []s2.WriterOption, but not with a nil s2.WriterOption, so
|
// with a nil []s2.WriterOption, but not with a nil s2.WriterOption, so
|
||||||
@@ -706,6 +749,7 @@ func NewServer(opts *Options) (*Server, error) {
|
|||||||
Headers: !opts.NoHeaderSupport,
|
Headers: !opts.NoHeaderSupport,
|
||||||
Cluster: opts.Cluster.Name,
|
Cluster: opts.Cluster.Name,
|
||||||
Domain: opts.JetStreamDomain,
|
Domain: opts.JetStreamDomain,
|
||||||
|
JSApiLevel: JSApiLevel,
|
||||||
}
|
}
|
||||||
|
|
||||||
if tlsReq && !info.TLSRequired {
|
if tlsReq && !info.TLSRequired {
|
||||||
@@ -774,6 +818,11 @@ func NewServer(opts *Options) (*Server, error) {
|
|||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
// If there are proxies trusted public keys in the configuration
|
||||||
|
// this will fill create the corresponding list of nkeys.KeyPair
|
||||||
|
// that we can use for signature verification.
|
||||||
|
s.processProxiesTrustedKeys()
|
||||||
|
|
||||||
// Place ourselves in the JetStream nodeInfo if needed.
|
// Place ourselves in the JetStream nodeInfo if needed.
|
||||||
if opts.JetStream {
|
if opts.JetStream {
|
||||||
ourNode := getHash(serverName)
|
ourNode := getHash(serverName)
|
||||||
@@ -1099,6 +1148,10 @@ func validateOptions(o *Options) error {
|
|||||||
if err := validateAuth(o); err != nil {
|
if err := validateAuth(o); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Check that proxies is properly configured.
|
||||||
|
if err := validateProxies(o); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// Check that gateway is properly configured. Returns no error
|
// Check that gateway is properly configured. Returns no error
|
||||||
// if there is no gateway defined.
|
// if there is no gateway defined.
|
||||||
if err := validateGatewayOptions(o); err != nil {
|
if err := validateGatewayOptions(o); err != nil {
|
||||||
@@ -2352,7 +2405,7 @@ func (s *Server) Start() {
|
|||||||
StoreDir: opts.StoreDir,
|
StoreDir: opts.StoreDir,
|
||||||
SyncInterval: opts.SyncInterval,
|
SyncInterval: opts.SyncInterval,
|
||||||
SyncAlways: opts.SyncAlways,
|
SyncAlways: opts.SyncAlways,
|
||||||
Strict: opts.JetStreamStrict,
|
Strict: !opts.NoJetStreamStrict,
|
||||||
MaxMemory: opts.JetStreamMaxMemory,
|
MaxMemory: opts.JetStreamMaxMemory,
|
||||||
MaxStore: opts.JetStreamMaxStore,
|
MaxStore: opts.JetStreamMaxStore,
|
||||||
Domain: opts.JetStreamDomain,
|
Domain: opts.JetStreamDomain,
|
||||||
@@ -3396,7 +3449,7 @@ func (s *Server) createClientEx(conn net.Conn, inProcess bool) *client {
|
|||||||
if tlsRequired {
|
if tlsRequired {
|
||||||
c.Debugf("TLS handshake complete")
|
c.Debugf("TLS handshake complete")
|
||||||
cs := c.nc.(*tls.Conn).ConnectionState()
|
cs := c.nc.(*tls.Conn).ConnectionState()
|
||||||
c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tlsCipher(cs.CipherSuite))
|
c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tls.CipherSuiteName(cs.CipherSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
@@ -3501,8 +3554,6 @@ func (s *Server) updateServerINFOAndSendINFOToClients(curls, wsurls []string, ad
|
|||||||
updateInfo(&s.info.WSConnectURLs, s.websocket.connectURLs, s.websocket.connectURLsMap)
|
updateInfo(&s.info.WSConnectURLs, s.websocket.connectURLs, s.websocket.connectURLsMap)
|
||||||
}
|
}
|
||||||
if cliUpdated || wsUpdated {
|
if cliUpdated || wsUpdated {
|
||||||
// Update the time of this update
|
|
||||||
s.lastCURLsUpdate = time.Now().UnixNano()
|
|
||||||
// Send to all registered clients that support async INFO protocols.
|
// Send to all registered clients that support async INFO protocols.
|
||||||
s.sendAsyncInfoToClients(cliUpdated, wsUpdated)
|
s.sendAsyncInfoToClients(cliUpdated, wsUpdated)
|
||||||
}
|
}
|
||||||
@@ -3554,15 +3605,6 @@ func tlsVersionFromString(ver string) (uint16, error) {
|
|||||||
return 0, fmt.Errorf("unknown version: %v", ver)
|
return 0, fmt.Errorf("unknown version: %v", ver)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We use hex here so we don't need multiple versions
|
|
||||||
func tlsCipher(cs uint16) string {
|
|
||||||
name, present := cipherMapByID[cs]
|
|
||||||
if present {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("Unknown [0x%x]", cs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a client or route from our internal accounting.
|
// Remove a client or route from our internal accounting.
|
||||||
func (s *Server) removeClient(c *client) {
|
func (s *Server) removeClient(c *client) {
|
||||||
// kind is immutable, so can check without lock
|
// kind is immutable, so can check without lock
|
||||||
@@ -3574,6 +3616,7 @@ func (s *Server) removeClient(c *client) {
|
|||||||
if c.kind == CLIENT && c.opts.Protocol >= ClientProtoInfo {
|
if c.kind == CLIENT && c.opts.Protocol >= ClientProtoInfo {
|
||||||
updateProtoInfoCount = true
|
updateProtoInfoCount = true
|
||||||
}
|
}
|
||||||
|
proxyKey := c.proxyKey
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
@@ -3581,6 +3624,9 @@ func (s *Server) removeClient(c *client) {
|
|||||||
if updateProtoInfoCount {
|
if updateProtoInfoCount {
|
||||||
s.cproto--
|
s.cproto--
|
||||||
}
|
}
|
||||||
|
if proxyKey != _EMPTY_ {
|
||||||
|
s.removeProxiedConn(proxyKey, cid)
|
||||||
|
}
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
case ROUTER:
|
case ROUTER:
|
||||||
s.removeRoute(c)
|
s.removeRoute(c)
|
||||||
@@ -3591,6 +3637,18 @@ func (s *Server) removeClient(c *client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the connection with id `cid` from the map of connections
|
||||||
|
// under the public key `key` of the trusted proxies.
|
||||||
|
//
|
||||||
|
// Server lock must be held on entry.
|
||||||
|
func (s *Server) removeProxiedConn(key string, cid uint64) {
|
||||||
|
conns := s.proxiedConns[key]
|
||||||
|
delete(conns, cid)
|
||||||
|
if len(conns) == 0 {
|
||||||
|
delete(s.proxiedConns, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) removeFromTempClients(cid uint64) {
|
func (s *Server) removeFromTempClients(cid uint64) {
|
||||||
s.grMu.Lock()
|
s.grMu.Lock()
|
||||||
delete(s.grTmpClients, cid)
|
delete(s.grTmpClients, cid)
|
||||||
@@ -3699,6 +3757,11 @@ func (s *Server) NumSlowConsumers() int64 {
|
|||||||
return atomic.LoadInt64(&s.slowConsumers)
|
return atomic.LoadInt64(&s.slowConsumers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NumStalledClients will report the total number of times clients have been stalled.
|
||||||
|
func (s *Server) NumStalledClients() int64 {
|
||||||
|
return atomic.LoadInt64(&s.stalls)
|
||||||
|
}
|
||||||
|
|
||||||
// NumSlowConsumersClients will report the number of slow consumers clients.
|
// NumSlowConsumersClients will report the number of slow consumers clients.
|
||||||
func (s *Server) NumSlowConsumersClients() uint64 {
|
func (s *Server) NumSlowConsumersClients() uint64 {
|
||||||
return s.scStats.clients.Load()
|
return s.scStats.clients.Load()
|
||||||
@@ -3719,6 +3782,31 @@ func (s *Server) NumSlowConsumersLeafs() uint64 {
|
|||||||
return s.scStats.leafs.Load()
|
return s.scStats.leafs.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NumStaleConnections will report the number of stale connections.
|
||||||
|
func (s *Server) NumStaleConnections() int64 {
|
||||||
|
return atomic.LoadInt64(&s.staleConnections)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumStaleConnectionsClients will report the number of stale client connections.
|
||||||
|
func (s *Server) NumStaleConnectionsClients() uint64 {
|
||||||
|
return s.staleStats.clients.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumStaleConnectionsRoutes will report the number of stale route connections.
|
||||||
|
func (s *Server) NumStaleConnectionsRoutes() uint64 {
|
||||||
|
return s.staleStats.routes.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumStaleConnectionsGateways will report the number of stale gateway connections.
|
||||||
|
func (s *Server) NumStaleConnectionsGateways() uint64 {
|
||||||
|
return s.staleStats.gateways.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumStaleConnectionsLeafs will report the number of stale leaf connections.
|
||||||
|
func (s *Server) NumStaleConnectionsLeafs() uint64 {
|
||||||
|
return s.staleStats.leafs.Load()
|
||||||
|
}
|
||||||
|
|
||||||
// ConfigTime will report the last time the server configuration was loaded.
|
// ConfigTime will report the last time the server configuration was loaded.
|
||||||
func (s *Server) ConfigTime() time.Time {
|
func (s *Server) ConfigTime() time.Time {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
|
|||||||
+9
-15
@@ -35,8 +35,6 @@ const (
|
|||||||
FileStorage = StorageType(22)
|
FileStorage = StorageType(22)
|
||||||
// MemoryStorage specifies in memory only.
|
// MemoryStorage specifies in memory only.
|
||||||
MemoryStorage = StorageType(33)
|
MemoryStorage = StorageType(33)
|
||||||
// Any is for internals.
|
|
||||||
AnyStorage = StorageType(44)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -86,14 +84,16 @@ type StorageUpdateHandler func(msgs, bytes int64, seq uint64, subj string)
|
|||||||
// Used to call back into the upper layers to remove a message.
|
// Used to call back into the upper layers to remove a message.
|
||||||
type StorageRemoveMsgHandler func(seq uint64)
|
type StorageRemoveMsgHandler func(seq uint64)
|
||||||
|
|
||||||
// Used to call back into the upper layers to report on newly created subject delete markers.
|
// Used to call back into the upper layers to process a JetStream message.
|
||||||
type SubjectDeleteMarkerUpdateHandler func(*inMsg)
|
// Will propose the message if the stream is replicated.
|
||||||
|
type ProcessJetStreamMsgHandler func(*inMsg)
|
||||||
|
|
||||||
type StreamStore interface {
|
type StreamStore interface {
|
||||||
StoreMsg(subject string, hdr, msg []byte, ttl int64) (uint64, int64, error)
|
StoreMsg(subject string, hdr, msg []byte, ttl int64) (uint64, int64, error)
|
||||||
StoreRawMsg(subject string, hdr, msg []byte, seq uint64, ts int64, ttl int64) error
|
StoreRawMsg(subject string, hdr, msg []byte, seq uint64, ts int64, ttl int64) error
|
||||||
SkipMsg() uint64
|
SkipMsg() uint64
|
||||||
SkipMsgs(seq uint64, num uint64) error
|
SkipMsgs(seq uint64, num uint64) error
|
||||||
|
FlushAllPending()
|
||||||
LoadMsg(seq uint64, sm *StoreMsg) (*StoreMsg, error)
|
LoadMsg(seq uint64, sm *StoreMsg) (*StoreMsg, error)
|
||||||
LoadNextMsg(filter string, wc bool, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error)
|
LoadNextMsg(filter string, wc bool, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error)
|
||||||
LoadNextMsgMulti(sl *gsl.SimpleSublist, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error)
|
LoadNextMsgMulti(sl *gsl.SimpleSublist, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error)
|
||||||
@@ -120,16 +120,17 @@ type StreamStore interface {
|
|||||||
SyncDeleted(dbs DeleteBlocks)
|
SyncDeleted(dbs DeleteBlocks)
|
||||||
Type() StorageType
|
Type() StorageType
|
||||||
RegisterStorageUpdates(StorageUpdateHandler)
|
RegisterStorageUpdates(StorageUpdateHandler)
|
||||||
RegisterStorageRemoveMsg(handler StorageRemoveMsgHandler)
|
RegisterStorageRemoveMsg(StorageRemoveMsgHandler)
|
||||||
RegisterSubjectDeleteMarkerUpdates(SubjectDeleteMarkerUpdateHandler)
|
RegisterProcessJetStreamMsg(ProcessJetStreamMsgHandler)
|
||||||
UpdateConfig(cfg *StreamConfig) error
|
UpdateConfig(cfg *StreamConfig) error
|
||||||
Delete() error
|
Delete(inline bool) error
|
||||||
Stop() error
|
Stop() error
|
||||||
ConsumerStore(name string, cfg *ConsumerConfig) (ConsumerStore, error)
|
ConsumerStore(name string, cfg *ConsumerConfig) (ConsumerStore, error)
|
||||||
AddConsumer(o ConsumerStore) error
|
AddConsumer(o ConsumerStore) error
|
||||||
RemoveConsumer(o ConsumerStore) error
|
RemoveConsumer(o ConsumerStore) error
|
||||||
Snapshot(deadline time.Duration, includeConsumers, checkMsgs bool) (*SnapshotResult, error)
|
Snapshot(deadline time.Duration, includeConsumers, checkMsgs bool) (*SnapshotResult, error)
|
||||||
Utilization() (total, reported uint64, err error)
|
Utilization() (total, reported uint64, err error)
|
||||||
|
ResetState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RetentionPolicy determines how messages in a set are retained.
|
// RetentionPolicy determines how messages in a set are retained.
|
||||||
@@ -461,6 +462,7 @@ type Pending struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TemplateStore stores templates.
|
// TemplateStore stores templates.
|
||||||
|
// Deprecated: stream templates are deprecated and will be removed in a future version.
|
||||||
type TemplateStore interface {
|
type TemplateStore interface {
|
||||||
Store(*streamTemplate) error
|
Store(*streamTemplate) error
|
||||||
Delete(*streamTemplate) error
|
Delete(*streamTemplate) error
|
||||||
@@ -555,13 +557,11 @@ func (dp *DiscardPolicy) UnmarshalJSON(data []byte) error {
|
|||||||
const (
|
const (
|
||||||
memoryStorageJSONString = `"memory"`
|
memoryStorageJSONString = `"memory"`
|
||||||
fileStorageJSONString = `"file"`
|
fileStorageJSONString = `"file"`
|
||||||
anyStorageJSONString = `"any"`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
memoryStorageJSONBytes = []byte(memoryStorageJSONString)
|
memoryStorageJSONBytes = []byte(memoryStorageJSONString)
|
||||||
fileStorageJSONBytes = []byte(fileStorageJSONString)
|
fileStorageJSONBytes = []byte(fileStorageJSONString)
|
||||||
anyStorageJSONBytes = []byte(anyStorageJSONString)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (st StorageType) String() string {
|
func (st StorageType) String() string {
|
||||||
@@ -570,8 +570,6 @@ func (st StorageType) String() string {
|
|||||||
return "Memory"
|
return "Memory"
|
||||||
case FileStorage:
|
case FileStorage:
|
||||||
return "File"
|
return "File"
|
||||||
case AnyStorage:
|
|
||||||
return "Any"
|
|
||||||
default:
|
default:
|
||||||
return "Unknown Storage Type"
|
return "Unknown Storage Type"
|
||||||
}
|
}
|
||||||
@@ -583,8 +581,6 @@ func (st StorageType) MarshalJSON() ([]byte, error) {
|
|||||||
return memoryStorageJSONBytes, nil
|
return memoryStorageJSONBytes, nil
|
||||||
case FileStorage:
|
case FileStorage:
|
||||||
return fileStorageJSONBytes, nil
|
return fileStorageJSONBytes, nil
|
||||||
case AnyStorage:
|
|
||||||
return anyStorageJSONBytes, nil
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("can not marshal %v", st)
|
return nil, fmt.Errorf("can not marshal %v", st)
|
||||||
}
|
}
|
||||||
@@ -596,8 +592,6 @@ func (st *StorageType) UnmarshalJSON(data []byte) error {
|
|||||||
*st = MemoryStorage
|
*st = MemoryStorage
|
||||||
case fileStorageJSONString:
|
case fileStorageJSONString:
|
||||||
*st = FileStorage
|
*st = FileStorage
|
||||||
case anyStorageJSONString:
|
|
||||||
*st = AnyStorage
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("can not unmarshal %q", data)
|
return fmt.Errorf("can not unmarshal %q", data)
|
||||||
}
|
}
|
||||||
|
|||||||
+1449
-293
File diff suppressed because it is too large
Load Diff
+67
-12
@@ -16,6 +16,8 @@ package server
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -33,6 +35,7 @@ var (
|
|||||||
splitMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]plit\s*\((.*)\)\s*}}`)
|
splitMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]plit\s*\((.*)\)\s*}}`)
|
||||||
leftMappingFunctionRegEx = regexp.MustCompile(`{{\s*[lL]eft\s*\((.*)\)\s*}}`)
|
leftMappingFunctionRegEx = regexp.MustCompile(`{{\s*[lL]eft\s*\((.*)\)\s*}}`)
|
||||||
rightMappingFunctionRegEx = regexp.MustCompile(`{{\s*[rR]ight\s*\((.*)\)\s*}}`)
|
rightMappingFunctionRegEx = regexp.MustCompile(`{{\s*[rR]ight\s*\((.*)\)\s*}}`)
|
||||||
|
randomMappingFunctionRegEx = regexp.MustCompile(`{{\s*[rR]andom\s*\((.*)\)\s*}}`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enum for the subject mapping subjectTransform function types
|
// Enum for the subject mapping subjectTransform function types
|
||||||
@@ -48,6 +51,7 @@ const (
|
|||||||
Split
|
Split
|
||||||
Left
|
Left
|
||||||
Right
|
Right
|
||||||
|
Random
|
||||||
)
|
)
|
||||||
|
|
||||||
// Transforms for arbitrarily mapping subjects from one to another for maps, tees and filters.
|
// Transforms for arbitrarily mapping subjects from one to another for maps, tees and filters.
|
||||||
@@ -123,17 +127,16 @@ func NewSubjectTransformWithStrict(src, dest string, strict bool) (*subjectTrans
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if npwcs == 0 {
|
|
||||||
if tranformType != NoTransform {
|
|
||||||
return nil, &mappingDestinationErr{token, ErrMappingDestinationIndexOutOfRange}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tranformType == NoTransform {
|
if tranformType == NoTransform {
|
||||||
dtokMappingFunctionTypes = append(dtokMappingFunctionTypes, NoTransform)
|
dtokMappingFunctionTypes = append(dtokMappingFunctionTypes, NoTransform)
|
||||||
dtokMappingFunctionTokenIndexes = append(dtokMappingFunctionTokenIndexes, []int{-1})
|
dtokMappingFunctionTokenIndexes = append(dtokMappingFunctionTokenIndexes, []int{-1})
|
||||||
dtokMappingFunctionIntArgs = append(dtokMappingFunctionIntArgs, -1)
|
dtokMappingFunctionIntArgs = append(dtokMappingFunctionIntArgs, -1)
|
||||||
dtokMappingFunctionStringArgs = append(dtokMappingFunctionStringArgs, _EMPTY_)
|
dtokMappingFunctionStringArgs = append(dtokMappingFunctionStringArgs, _EMPTY_)
|
||||||
|
} else if tranformType == Random {
|
||||||
|
dtokMappingFunctionTypes = append(dtokMappingFunctionTypes, Random)
|
||||||
|
dtokMappingFunctionTokenIndexes = append(dtokMappingFunctionTokenIndexes, []int{})
|
||||||
|
dtokMappingFunctionIntArgs = append(dtokMappingFunctionIntArgs, transfomArgInt)
|
||||||
|
dtokMappingFunctionStringArgs = append(dtokMappingFunctionStringArgs, _EMPTY_)
|
||||||
} else {
|
} else {
|
||||||
nphs += len(transformArgWildcardIndexes)
|
nphs += len(transformArgWildcardIndexes)
|
||||||
// Now build up our runtime mapping from dest to source tokens.
|
// Now build up our runtime mapping from dest to source tokens.
|
||||||
@@ -158,12 +161,22 @@ func NewSubjectTransformWithStrict(src, dest string, strict bool) (*subjectTrans
|
|||||||
} else {
|
} else {
|
||||||
// no wildcards used in the source: check that no transform functions are used in the destination
|
// no wildcards used in the source: check that no transform functions are used in the destination
|
||||||
for _, token := range dtokens {
|
for _, token := range dtokens {
|
||||||
tranformType, _, _, _, err := indexPlaceHolders(token)
|
tranformType, _, transfomArgInt, _, err := indexPlaceHolders(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if tranformType != NoTransform {
|
if tranformType == NoTransform {
|
||||||
|
dtokMappingFunctionTypes = append(dtokMappingFunctionTypes, NoTransform)
|
||||||
|
dtokMappingFunctionTokenIndexes = append(dtokMappingFunctionTokenIndexes, []int{-1})
|
||||||
|
dtokMappingFunctionIntArgs = append(dtokMappingFunctionIntArgs, -1)
|
||||||
|
dtokMappingFunctionStringArgs = append(dtokMappingFunctionStringArgs, _EMPTY_)
|
||||||
|
} else if tranformType == Random || tranformType == Partition {
|
||||||
|
dtokMappingFunctionTypes = append(dtokMappingFunctionTypes, tranformType)
|
||||||
|
dtokMappingFunctionTokenIndexes = append(dtokMappingFunctionTokenIndexes, []int{})
|
||||||
|
dtokMappingFunctionIntArgs = append(dtokMappingFunctionIntArgs, transfomArgInt)
|
||||||
|
dtokMappingFunctionStringArgs = append(dtokMappingFunctionStringArgs, _EMPTY_)
|
||||||
|
} else {
|
||||||
return nil, &mappingDestinationErr{token, ErrMappingDestinationIndexOutOfRange}
|
return nil, &mappingDestinationErr{token, ErrMappingDestinationIndexOutOfRange}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,12 +268,19 @@ func indexPlaceHolders(token string) (int16, []int, int32, string, error) {
|
|||||||
// partition(number of partitions, token1, token2, ...)
|
// partition(number of partitions, token1, token2, ...)
|
||||||
args = getMappingFunctionArgs(partitionMappingFunctionRegEx, token)
|
args = getMappingFunctionArgs(partitionMappingFunctionRegEx, token)
|
||||||
if args != nil {
|
if args != nil {
|
||||||
if len(args) < 2 {
|
if len(args) < 1 {
|
||||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationNotEnoughArgs}
|
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationNotEnoughArgs}
|
||||||
}
|
}
|
||||||
|
if len(args) == 1 {
|
||||||
|
mappingFunctionIntArg, err := strconv.Atoi(strings.Trim(args[0], " "))
|
||||||
|
if err != nil || mappingFunctionIntArg > math.MaxInt32 {
|
||||||
|
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationInvalidArg}
|
||||||
|
}
|
||||||
|
return Partition, []int{}, int32(mappingFunctionIntArg), _EMPTY_, nil
|
||||||
|
}
|
||||||
if len(args) >= 2 {
|
if len(args) >= 2 {
|
||||||
mappingFunctionIntArg, err := strconv.Atoi(strings.Trim(args[0], " "))
|
mappingFunctionIntArg, err := strconv.Atoi(strings.Trim(args[0], " "))
|
||||||
if err != nil {
|
if err != nil || mappingFunctionIntArg > math.MaxInt32 {
|
||||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationInvalidArg}
|
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationInvalidArg}
|
||||||
}
|
}
|
||||||
var numPositions = len(args[1:])
|
var numPositions = len(args[1:])
|
||||||
@@ -333,6 +353,19 @@ func indexPlaceHolders(token string) (int16, []int, int32, string, error) {
|
|||||||
return Split, []int{i}, -1, args[1], nil
|
return Split, []int{i}, -1, args[1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Random(max)
|
||||||
|
args = getMappingFunctionArgs(randomMappingFunctionRegEx, token)
|
||||||
|
if args != nil {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationNotEnoughArgs}
|
||||||
|
}
|
||||||
|
mappingFunctionIntArg, err := strconv.Atoi(strings.Trim(args[0], " "))
|
||||||
|
if err != nil || mappingFunctionIntArg > math.MaxInt32 {
|
||||||
|
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationInvalidArg}
|
||||||
|
}
|
||||||
|
return Random, []int{}, int32(mappingFunctionIntArg), _EMPTY_, nil
|
||||||
|
}
|
||||||
|
|
||||||
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrUnknownMappingDestinationFunction}
|
return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrUnknownMappingDestinationFunction}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,7 +456,21 @@ func (tr *subjectTransform) TransformSubject(subject string) string {
|
|||||||
return tr.TransformTokenizedSubject(tokenizeSubject(subject))
|
return tr.TransformTokenizedSubject(tokenizeSubject(subject))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tr *subjectTransform) getRandomPartition(ceiling int) string {
|
||||||
|
// Avoid an integer divide by zero panic below.
|
||||||
|
if ceiling == 0 {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
return strconv.Itoa(int(rand.Int31()) % ceiling)
|
||||||
|
}
|
||||||
|
|
||||||
func (tr *subjectTransform) getHashPartition(key []byte, numBuckets int) string {
|
func (tr *subjectTransform) getHashPartition(key []byte, numBuckets int) string {
|
||||||
|
// Avoid an integer divide by zero panic below.
|
||||||
|
if numBuckets == 0 {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
h := fnv.New32a()
|
h := fnv.New32a()
|
||||||
_, _ = h.Write(key)
|
_, _ = h.Write(key)
|
||||||
|
|
||||||
@@ -454,8 +501,14 @@ func (tr *subjectTransform) TransformTokenizedSubject(tokens []string) string {
|
|||||||
_buffer [64]byte
|
_buffer [64]byte
|
||||||
keyForHashing = _buffer[:0]
|
keyForHashing = _buffer[:0]
|
||||||
)
|
)
|
||||||
for _, sourceToken := range tr.dtokmftokindexesargs[i] {
|
if len(tr.dtokmftokindexesargs[i]) > 0 {
|
||||||
keyForHashing = append(keyForHashing, []byte(tokens[sourceToken])...)
|
// When token positions are specified.
|
||||||
|
for _, sourceToken := range tr.dtokmftokindexesargs[i] {
|
||||||
|
keyForHashing = append(keyForHashing, []byte(tokens[sourceToken])...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// When using the shorthand partition(n).
|
||||||
|
keyForHashing = append(keyForHashing, strings.Join(tokens, ".")...)
|
||||||
}
|
}
|
||||||
b.WriteString(tr.getHashPartition(keyForHashing, int(tr.dtokmfintargs[i])))
|
b.WriteString(tr.getHashPartition(keyForHashing, int(tr.dtokmfintargs[i])))
|
||||||
case Wildcard: // simple substitution
|
case Wildcard: // simple substitution
|
||||||
@@ -558,6 +611,8 @@ func (tr *subjectTransform) TransformTokenizedSubject(tokens []string) string {
|
|||||||
} else { // too small to slice at the requested size: don't slice
|
} else { // too small to slice at the requested size: don't slice
|
||||||
b.WriteString(sourceToken)
|
b.WriteString(sourceToken)
|
||||||
}
|
}
|
||||||
|
case Random:
|
||||||
|
b.WriteString(tr.getRandomPartition(int(tr.dtokmfintargs[i])))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -1292,7 +1292,8 @@ func ValidateMapping(src string, dest string) error {
|
|||||||
!splitFromRightMappingFunctionRegEx.MatchString(t) &&
|
!splitFromRightMappingFunctionRegEx.MatchString(t) &&
|
||||||
!sliceFromLeftMappingFunctionRegEx.MatchString(t) &&
|
!sliceFromLeftMappingFunctionRegEx.MatchString(t) &&
|
||||||
!sliceFromRightMappingFunctionRegEx.MatchString(t) &&
|
!sliceFromRightMappingFunctionRegEx.MatchString(t) &&
|
||||||
!splitMappingFunctionRegEx.MatchString(t) {
|
!splitMappingFunctionRegEx.MatchString(t) &&
|
||||||
|
!randomMappingFunctionRegEx.MatchString(t) {
|
||||||
return &mappingDestinationErr{t, ErrUnknownMappingDestinationFunction}
|
return &mappingDestinationErr{t, ErrUnknownMappingDestinationFunction}
|
||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
|
|||||||
+1
-1
@@ -211,7 +211,7 @@ func (hw *HashWheel) Count() uint64 {
|
|||||||
return hw.count
|
return hw.count
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppendEncode writes out the contents of the THW into a binary snapshot
|
// Encode writes out the contents of the THW into a binary snapshot
|
||||||
// and returns it. The high seq number is included in the snapshot and will
|
// and returns it. The high seq number is included in the snapshot and will
|
||||||
// be returned on decode.
|
// be returned on decode.
|
||||||
func (hw *HashWheel) Encode(highSeq uint64) []byte {
|
func (hw *HashWheel) Encode(highSeq uint64) []byte {
|
||||||
|
|||||||
+1
-1
@@ -312,7 +312,7 @@ func getURLsAsString(urls []*url.URL) []string {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyBytes make a new slice of the same size than `src` and copy its content.
|
// copyBytes make a new slice of the same size as `src` and copy its content.
|
||||||
// If `src` is nil or its length is 0, then this returns `nil`
|
// If `src` is nil or its length is 0, then this returns `nil`
|
||||||
func copyBytes(src []byte) []byte {
|
func copyBytes(src []byte) []byte {
|
||||||
if len(src) == 0 {
|
if len(src) == 0 {
|
||||||
|
|||||||
+6
-3
@@ -1147,21 +1147,24 @@ func (s *Server) startWebsocketServer() {
|
|||||||
// regardless of NoTLS. If we don't have a TLS config, it means that the
|
// regardless of NoTLS. If we don't have a TLS config, it means that the
|
||||||
// user has configured NoTLS because otherwise the server would have failed
|
// user has configured NoTLS because otherwise the server would have failed
|
||||||
// to start due to options validation.
|
// to start due to options validation.
|
||||||
|
var config *tls.Config
|
||||||
if o.TLSConfig != nil {
|
if o.TLSConfig != nil {
|
||||||
proto = wsSchemePrefixTLS
|
proto = wsSchemePrefixTLS
|
||||||
config := o.TLSConfig.Clone()
|
config = o.TLSConfig.Clone()
|
||||||
config.GetConfigForClient = s.wsGetTLSConfig
|
config.GetConfigForClient = s.wsGetTLSConfig
|
||||||
hl, err = tls.Listen("tcp", hp, config)
|
|
||||||
} else {
|
} else {
|
||||||
proto = wsSchemePrefix
|
proto = wsSchemePrefix
|
||||||
hl, err = net.Listen("tcp", hp)
|
|
||||||
}
|
}
|
||||||
|
hl, err = natsListen("tcp", hp)
|
||||||
s.websocket.listenerErr = err
|
s.websocket.listenerErr = err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
s.Fatalf("Unable to listen for websocket connections: %v", err)
|
s.Fatalf("Unable to listen for websocket connections: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if config != nil {
|
||||||
|
hl = tls.NewListener(hl, config)
|
||||||
|
}
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
o.Port = hl.Addr().(*net.TCPAddr).Port
|
o.Port = hl.Addr().(*net.TCPAddr).Port
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+3
-2
@@ -1105,10 +1105,10 @@ github.com/mschoch/smat
|
|||||||
# github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
|
# github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
|
||||||
## explicit
|
## explicit
|
||||||
github.com/munnerz/goautoneg
|
github.com/munnerz/goautoneg
|
||||||
# github.com/nats-io/jwt/v2 v2.7.4
|
# github.com/nats-io/jwt/v2 v2.8.0
|
||||||
## explicit; go 1.23.0
|
## explicit; go 1.23.0
|
||||||
github.com/nats-io/jwt/v2
|
github.com/nats-io/jwt/v2
|
||||||
# github.com/nats-io/nats-server/v2 v2.11.9
|
# github.com/nats-io/nats-server/v2 v2.12.0
|
||||||
## explicit; go 1.24.0
|
## explicit; go 1.24.0
|
||||||
github.com/nats-io/nats-server/v2/conf
|
github.com/nats-io/nats-server/v2/conf
|
||||||
github.com/nats-io/nats-server/v2/internal/fastrand
|
github.com/nats-io/nats-server/v2/internal/fastrand
|
||||||
@@ -1119,6 +1119,7 @@ github.com/nats-io/nats-server/v2/server/ats
|
|||||||
github.com/nats-io/nats-server/v2/server/avl
|
github.com/nats-io/nats-server/v2/server/avl
|
||||||
github.com/nats-io/nats-server/v2/server/certidp
|
github.com/nats-io/nats-server/v2/server/certidp
|
||||||
github.com/nats-io/nats-server/v2/server/certstore
|
github.com/nats-io/nats-server/v2/server/certstore
|
||||||
|
github.com/nats-io/nats-server/v2/server/elastic
|
||||||
github.com/nats-io/nats-server/v2/server/gsl
|
github.com/nats-io/nats-server/v2/server/gsl
|
||||||
github.com/nats-io/nats-server/v2/server/pse
|
github.com/nats-io/nats-server/v2/server/pse
|
||||||
github.com/nats-io/nats-server/v2/server/stree
|
github.com/nats-io/nats-server/v2/server/stree
|
||||||
|
|||||||
Reference in New Issue
Block a user