├── .gitignore ├── .goreleaser.yml ├── .travis.yml ├── CODE-OF-CONDUCT.md ├── GOVERNANCE.md ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── TODO.md ├── dependencies.md ├── go.mod ├── go.sum ├── logger ├── logger.go └── logger_test.go ├── nats-streaming-server.go ├── scripts ├── cov.sh ├── drop_postgres.db.sql ├── mysql.db.sql └── postgres.db.sql ├── server ├── bench_test.go ├── client.go ├── client_test.go ├── clustering.go ├── clustering_test.go ├── conf.go ├── conf_test.go ├── ft.go ├── ft_test.go ├── monitor.go ├── monitor_test.go ├── partitions.go ├── partitions_test.go ├── raft_log.go ├── raft_log_test.go ├── raft_transport.go ├── raft_transport_test.go ├── server.go ├── server_clients_test.go ├── server_delivery_test.go ├── server_durable_test.go ├── server_limits_test.go ├── server_queue_test.go ├── server_redelivery_test.go ├── server_req_test.go ├── server_run_test.go ├── server_storefailures_test.go ├── server_sub_test.go ├── server_test.go ├── service.go ├── service_test.go ├── service_windows.go ├── signal.go ├── signal_test.go ├── signal_windows.go └── snapshot.go ├── spb ├── protocol.pb.go └── protocol.proto ├── stores ├── bench_test.go ├── common.go ├── common_msg_test.go ├── common_sub_test.go ├── common_test.go ├── cryptostore.go ├── cryptostore_test.go ├── filestore.go ├── filestore_msg_test.go ├── filestore_sub_test.go ├── filestore_test.go ├── limits.go ├── limits_test.go ├── memstore.go ├── memstore_test.go ├── pqdeadlines │ └── pqdeadlines.go ├── raftstore.go ├── raftstore_test.go ├── sqlstore.go ├── sqlstore_test.go └── store.go ├── test ├── certs │ ├── ca.pem │ ├── client-cert.pem │ ├── client-key.pem │ ├── server-cert.pem │ ├── server-key-noip.pem │ ├── server-key.pem │ └── server-noip.pem ├── configs │ ├── multi_user.conf │ └── test_parse.conf ├── sqlstore.go └── test.go └── util ├── channels.go ├── lockfile_test.go ├── lockfile_unix.go ├── lockfile_win.go ├── no_race.go ├── race.go ├── sublist.go ├── sublist_test.go ├── util.go └── util_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | # bin 23 | nats-streaming-server 24 | 25 | *.exe 26 | *.test 27 | *.prof 28 | 29 | # Eclipse stuff 30 | .project 31 | .settings/ 32 | 33 | # MS Visual Code 34 | .vscode/ 35 | 36 | # Intellij 37 | .idea/ 38 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: nats-streaming-server 2 | 3 | release: 4 | github: 5 | owner: nats-io 6 | name: nats-streaming-server 7 | name_template: 'Release {{.Tag}}' 8 | draft: true 9 | 10 | changelog: 11 | skip: true 12 | 13 | builds: 14 | - main: . 15 | binary: nats-streaming-server 16 | flags: 17 | - -trimpath 18 | ldflags: 19 | - -w -X github.com/nats-io/nats-streaming-server/server.gitCommit={{.ShortCommit}} -X github.com/nats-io/nats-server/v2/server.gitCommit=e43cfb4 20 | env: 21 | - GO111MODULE=on 22 | - CGO_ENABLED=0 23 | goos: 24 | - darwin 25 | - linux 26 | - windows 27 | goarch: 28 | - amd64 29 | - arm 30 | - arm64 31 | - 386 32 | goarm: 33 | - 6 34 | - 7 35 | ignore: 36 | - goos: darwin 37 | goarch: 386 38 | 39 | nfpms: 40 | - file_name_template: '{{.ProjectName}}-{{.Tag}}-{{.Arch}}{{if .Arm}}{{.Arm}}{{end}}' 41 | homepage: https://nats.io 42 | description: High-Performance Streaming server for NATS, the cloud native messaging system. 43 | maintainer: Ivan Kozlovic 44 | license: Apache 2.0 45 | vendor: Synadia Inc. 46 | formats: 47 | - deb 48 | - rpm 49 | 50 | archives: 51 | - name_template: '{{.ProjectName}}-{{.Tag}}-{{.Os}}-{{.Arch}}{{if .Arm}}{{.Arm}}{{end}}' 52 | wrap_in_directory: true 53 | format: zip 54 | files: 55 | - README.md 56 | - LICENSE 57 | - name_template: '{{.ProjectName}}-{{.Tag}}-{{.Os}}-{{.Arch}}{{if .Arm}}{{.Arm}}{{end}}' 58 | id: targz-archives 59 | wrap_in_directory: true 60 | format: tar.gz 61 | files: 62 | - README.md 63 | - LICENSE 64 | 65 | checksum: 66 | name_template: 'SHA256SUMS' 67 | algorithm: sha256 68 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - windows 4 | vm: 5 | size: 2x-large 6 | language: go 7 | go: 8 | - "1.21.4" 9 | - "1.20.11" 10 | addons: 11 | apt: 12 | packages: 13 | - rpm 14 | env: 15 | - GO111MODULE=on 16 | go_import_path: github.com/nats-io/nats-streaming-server 17 | install: 18 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then 19 | if [[ "$TRAVIS_GO_VERSION" =~ 1.20 ]]; then 20 | go install honnef.co/go/tools/cmd/staticcheck@latest; 21 | go install github.com/client9/misspell/cmd/misspell@latest; 22 | fi; 23 | fi 24 | services: 25 | - mysql 26 | before_script: 27 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then 28 | GO_LIST=$(go list ./... | grep -v "/spb"); 29 | go install; 30 | $(exit $(go fmt $GO_LIST | wc -l)); 31 | go vet $GO_LIST; 32 | if [[ "$TRAVIS_GO_VERSION" =~ 1.20 ]]; then 33 | find . -type f -name "*.go" | grep -v "/spb/" | xargs misspell -error -locale US; 34 | staticcheck $GO_LIST; 35 | fi; 36 | fi 37 | script: 38 | - set -e 39 | - if [[ $TRAVIS_TAG ]]; then go test -v -run=TestVersionMatchesTag ./server -sql=false; fi 40 | - if [[ ! $TRAVIS_TAG ]]; then 41 | if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then 42 | mysql -u root -e "CREATE USER 'nss'@'localhost' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON *.* TO 'nss'@'localhost'; CREATE DATABASE test_nats_streaming;"; 43 | if [[ "$TRAVIS_GO_VERSION" =~ 1.21 ]]; then 44 | ./scripts/cov.sh TRAVIS; 45 | else 46 | go test -v -p=1 ./... -count=1 -vet=off -failfast; 47 | fi; 48 | fi; 49 | fi 50 | - if [[ ! $TRAVIS_TAG ]]; then 51 | if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then 52 | if [[ "$TRAVIS_GO_VERSION" =~ 1.21 ]]; then 53 | go build; 54 | else 55 | go install; 56 | go test -v -p=1 ./... -count=1 -vet=off -sql=false -failfast; 57 | fi; 58 | fi; 59 | fi 60 | - set +e 61 | after_success: 62 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then 63 | if [[ ! $TRAVIS_TAG ]]; then if [[ "$TRAVIS_GO_VERSION" =~ 1.21 ]]; then $HOME/gopath/bin/goveralls -coverprofile=acc.out -service travis-ci; fi; fi; 64 | fi 65 | 66 | deploy: 67 | provider: script 68 | cleanup: true 69 | script: curl -sL http://git.io/goreleaser | bash 70 | on: 71 | tags: true 72 | condition: ("$TRAVIS_OS_NAME" = "linux") && ("$TRAVIS_GO_VERSION" =~ "1.20") 73 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Community Code of Conduct 2 | 3 | NATS follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). 4 | -------------------------------------------------------------------------------- /GOVERNANCE.md: -------------------------------------------------------------------------------- 1 | # NATS Streaming Server Governance 2 | 3 | NATS Streaming Server is part of the NATS project and is subject to the [NATS Governance](https://github.com/nats-io/nats-general/blob/master/GOVERNANCE.md). -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | Maintainership is on a per project basis. 4 | 5 | ### Maintainers 6 | - Derek Collison [@derekcollison](https://github.com/derekcollison) 7 | - Ivan Kozlovic [@kozlovic](https://github.com/kozlovic) 8 | - R.I. Pienaar [@ripienaar](https://github.com/ripienaar) 9 | - Alberto Ricart [@aricart](https://github.com/aricart) 10 | - Colin Sullivan [@ColinSullivan1](https://github.com/ColinSullivan1) 11 | - Waldemar Quevedo [@wallyqs](https://github.com/wallyqs) 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NATS Streaming Server 2 | 3 | NATS Streaming is an extremely performant, lightweight reliable streaming platform built on [NATS](https://nats.io). 4 | 5 | [![License][License-Image]][License-Url] [![ReportCard][ReportCard-Image]][ReportCard-Url] [![Build][Build-Status-Image]][Build-Status-Url] [![Release][Release-Image]][Release-Url] [![Coverage][Coverage-Image]][Coverage-Url] 6 | 7 | # WARNING: Deprecation Notice :warning: 8 | 9 | The NATS Streaming Server is being deprecated. Critical bug fixes and security fixes will be applied until June of 2023. NATS enabled applications requiring persistence should use [JetStream](https://docs.nats.io/nats-concepts/jetstream). 10 | 11 | ## Documentation 12 | 13 | * [Official documentation](https://nats-io.gitbook.io/legacy-nats-docs/nats-streaming-server-aka-stan) 14 | 15 | ## Clients 16 | 17 | You can find [here](https://nats.io/download/) the list of NATS Streaming clients supported by Synadia. There are also links to community-contributed clients. 18 | 19 | ## Contact 20 | 21 | * [Twitter](https://twitter.com/nats_io): Follow us on Twitter! 22 | * [Google Groups](https://groups.google.com/forum/#!forum/natsio): Where you can ask questions 23 | * [Slack](https://natsio.slack.com): To join go [here](https://slack.nats.io). You can ask questions to our maintainers and to the rich and active community. 24 | 25 | ## Contributing 26 | 27 | If you are interested in contributing to NATS, read about our... 28 | 29 | * [Contributing guide](https://nats.io/community/#contribute) 30 | * [Report issues or propose Pull Requests](https://github.com/nats-io/nats-streaming-server) 31 | 32 | ## Security 33 | 34 | If you've found a vulnerability or a potential vulnerability in the NATS server, please let us know at 35 | [nats-security](mailto:security@nats.io). 36 | 37 | ## License 38 | 39 | Unless otherwise noted, the NATS source files are distributed 40 | under the Apache Version 2.0 license found in the LICENSE file. 41 | 42 | 43 | [License-Url]: https://www.apache.org/licenses/LICENSE-2.0 44 | [License-Image]: https://img.shields.io/badge/License-Apache2-blue.svg 45 | [Build-Status-Url]: https://travis-ci.com/github/nats-io/nats-streaming-server 46 | [Build-Status-Image]: https://travis-ci.com/nats-io/nats-streaming-server.svg?branch=main 47 | [Coverage-Url]: https://coveralls.io/r/nats-io/nats-streaming-server?branch=main 48 | [Coverage-image]: https://coveralls.io/repos/github/nats-io/nats-streaming-server/badge.svg?branch=main&t=kIxrDE 49 | [ReportCard-Url]: http://goreportcard.com/report/nats-io/nats-streaming-server 50 | [ReportCard-Image]: http://goreportcard.com/badge/github.com/nats-io/nats-streaming-server 51 | [Release-Url]: https://github.com/nats-io/nats-streaming-server/releases/tag/v0.25.6 52 | [Release-image]: https://img.shields.io/badge/release-v0.25.6-1eb0fc.svg 53 | [github-release]: https://github.com/nats-io/nats-streaming-server/releases/ 54 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | - [ ] Retry limits? 3 | - [X] Server Store Limits (time, msgs, byte) 4 | - [X] Change time to deltas 5 | - [X] Server heartbeat, release dead clients. 6 | - [X] Require clientID for published messages, error if not registered. 7 | - [X] Check for need of ackMap (out of order re-delivery to queue subscribers). 8 | - [X] Redelivered Flag for Msg. 9 | - [X] Queue Subscribers 10 | - [X] Durable Subscribers (survive reconnect, etc) 11 | - [X] Start Positions on Subscribers 12 | - [X] Ack for delivered just Reply? No need on ConnectedResponse? 13 | - [X] PublishWithReply, or option. 14 | - [X] Data Races in Server. 15 | - [X] Manual Ack? 16 | -------------------------------------------------------------------------------- /dependencies.md: -------------------------------------------------------------------------------- 1 | # External Dependencies 2 | 3 | This file lists the dependencies used in this repository. 4 | 5 | | Dependency | License | 6 | |-|-| 7 | | Go | BSD 3-Clause "New" or "Revised" License | 8 | | github.com/nats-io/nats-streaming-server | Apache License 2.0 | 9 | | github.com/go-sql-driver/mysql v1.7.0 | Mozilla Public License 2.0 | 10 | | github.com/gogo/protobuf v1.3.2 | Go | 11 | | github.com/hashicorp/go-hclog v1.5.0 | MIT | 12 | | github.com/hashicorp/go-msgpack/v2 v2.1.0 | MIT | 13 | | github.com/hashicorp/raft v1.4.0 | Mozilla Public License 2.0 | 14 | | github.com/lib/pq v1.10.7 | [MIT-Like](https://github.com/lib/pq/blob/master/LICENSE.md) | 15 | | github.com/nats-io/nats-server/v2 v2.9.15 | Apache License 2.0 | 16 | | github.com/nats-io/nats.go v1.25.0 | Apache License 2.0 | 17 | | github.com/nats-io/nuid v1.0.1 | Apache License 2.0 | 18 | | github.com/nats-io/stan.go v0.10.4 | Apache License 2.0 | 19 | | github.com/prometheus/procfs v0.8.0 | Apache License 2.0 | 20 | | go.etcd.io/bbolt v1.3.6 | MIT | 21 | | golang.org/x/crypto v0.6.0 | BSD 3-Clause "New" or "Revised" License | 22 | | golang.org/x/sys v0.5.0 | BSD 3-Clause "New" or "Revised" License | 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nats-io/nats-streaming-server 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-sql-driver/mysql v1.7.1 7 | github.com/gogo/protobuf v1.3.2 8 | github.com/hashicorp/go-hclog v1.5.0 9 | github.com/hashicorp/go-msgpack/v2 v2.1.1 10 | github.com/hashicorp/raft v1.6.0 11 | github.com/lib/pq v1.10.9 12 | github.com/nats-io/nats-server/v2 v2.9.24 13 | github.com/nats-io/nats.go v1.31.0 14 | github.com/nats-io/nuid v1.0.1 15 | github.com/nats-io/stan.go v0.10.4 16 | github.com/prometheus/procfs v0.12.0 17 | go.etcd.io/bbolt v1.3.8 18 | golang.org/x/crypto v0.15.0 19 | golang.org/x/sys v0.14.0 20 | ) 21 | 22 | require ( 23 | github.com/armon/go-metrics v0.4.1 // indirect 24 | github.com/fatih/color v1.16.0 // indirect 25 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 26 | github.com/hashicorp/golang-lru v1.0.2 // indirect 27 | github.com/klauspost/compress v1.17.3 // indirect 28 | github.com/mattn/go-colorable v0.1.13 // indirect 29 | github.com/mattn/go-isatty v0.0.20 // indirect 30 | github.com/minio/highwayhash v1.0.2 // indirect 31 | github.com/nats-io/jwt/v2 v2.5.3 // indirect 32 | github.com/nats-io/nkeys v0.4.6 // indirect 33 | golang.org/x/time v0.4.0 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2020 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package logger 15 | 16 | import ( 17 | "io" 18 | "sync" 19 | 20 | natsdLogger "github.com/nats-io/nats-server/v2/logger" 21 | natsd "github.com/nats-io/nats-server/v2/server" 22 | ) 23 | 24 | // LogPrefix is prefixed to all NATS Streaming log messages 25 | const LogPrefix = "STREAM: " 26 | 27 | // Logger interface for the Streaming project. 28 | // This is an alias of the NATS Server's Logger interface. 29 | type Logger natsd.Logger 30 | 31 | // StanLogger is the logger used in this project and implements 32 | // the Logger interface. 33 | type StanLogger struct { 34 | mu sync.RWMutex 35 | ns *natsd.Server 36 | debug bool 37 | trace bool 38 | ltime bool 39 | lfile string 40 | fszl int64 41 | ndbg bool 42 | ntrc bool 43 | log natsd.Logger 44 | } 45 | 46 | // NewStanLogger returns an instance of StanLogger 47 | func NewStanLogger() *StanLogger { 48 | return &StanLogger{} 49 | } 50 | 51 | // SetLogger sets the logger, debug and trace 52 | // DEPRECATED: Use SetLoggerWithOpts instead. 53 | func (s *StanLogger) SetLogger(log Logger, logtime, debug, trace bool, logfile string) { 54 | s.mu.Lock() 55 | s.log = log 56 | s.ltime = logtime 57 | s.debug = debug 58 | s.trace = trace 59 | s.lfile = logfile 60 | s.mu.Unlock() 61 | } 62 | 63 | // SetFileSizeLimit sets the size limit for a logfile 64 | // DEPRECATED: Use SetLoggerWithOpts instead. 65 | func (s *StanLogger) SetFileSizeLimit(limit int64) { 66 | s.mu.Lock() 67 | s.fszl = limit 68 | s.mu.Unlock() 69 | } 70 | 71 | // SetLoggerWithOpts sets the logger and various options. 72 | // Note that `debug` and `trace` here are for STAN. 73 | func (s *StanLogger) SetLoggerWithOpts(log Logger, nOpts *natsd.Options, debug, trace bool) { 74 | s.mu.Lock() 75 | s.log = log 76 | s.debug = debug 77 | s.trace = trace 78 | s.updateNATSOptions(nOpts) 79 | s.mu.Unlock() 80 | } 81 | 82 | func (s *StanLogger) updateNATSOptions(nOpts *natsd.Options) { 83 | s.ltime = nOpts.Logtime 84 | s.lfile = nOpts.LogFile 85 | s.fszl = nOpts.LogSizeLimit 86 | s.ndbg = nOpts.Debug 87 | s.ntrc = nOpts.Trace 88 | } 89 | 90 | // UpdateNATSOptions refreshes the NATS related options, for instance after a 91 | // configuration reload, so that if ReopenLogFile() is called, the logger new 92 | // options are applied. 93 | func (s *StanLogger) UpdateNATSOptions(nOpts *natsd.Options) { 94 | s.mu.Lock() 95 | s.updateNATSOptions(nOpts) 96 | s.mu.Unlock() 97 | } 98 | 99 | // SetNATSServer allows the logger to have a handle to the embedded NATS Server. 100 | // This sets the logger for the NATS Server and used during logfile re-opening. 101 | func (s *StanLogger) SetNATSServer(ns *natsd.Server) { 102 | s.mu.Lock() 103 | s.ns = ns 104 | ns.SetLogger(s.log, s.ndbg, s.ntrc) 105 | s.mu.Unlock() 106 | } 107 | 108 | // GetLogger returns the logger 109 | func (s *StanLogger) GetLogger() Logger { 110 | s.mu.RLock() 111 | l := s.log 112 | s.mu.RUnlock() 113 | return l 114 | } 115 | 116 | // ReopenLogFile closes and reopen the logfile. 117 | // Does nothing if the logger is not a file based. 118 | func (s *StanLogger) ReopenLogFile() { 119 | s.mu.Lock() 120 | if s.lfile == "" { 121 | s.mu.Unlock() 122 | s.Noticef("File log re-open ignored, not a file logger") 123 | return 124 | } 125 | if l, ok := s.log.(io.Closer); ok { 126 | if err := l.Close(); err != nil { 127 | s.mu.Unlock() 128 | s.Errorf("Unable to close logger: %v", err) 129 | return 130 | } 131 | } 132 | // Pass true for debug and trace here. The higher level will suppress the debug/traces if needed. 133 | fileLog := natsdLogger.NewFileLogger(s.lfile, s.ltime, true, true, true) 134 | if s.fszl > 0 { 135 | fileLog.SetSizeLimit(s.fszl) 136 | } 137 | if s.ns != nil { 138 | s.ns.SetLogger(fileLog, s.ndbg, s.ntrc) 139 | } 140 | s.log = fileLog 141 | s.mu.Unlock() 142 | s.Noticef("File log re-opened") 143 | } 144 | 145 | // Close closes this logger, releasing possible held resources. 146 | func (s *StanLogger) Close() error { 147 | s.mu.Lock() 148 | defer s.mu.Unlock() 149 | if l, ok := s.log.(io.Closer); ok { 150 | return l.Close() 151 | } 152 | return nil 153 | } 154 | 155 | // Noticef logs a notice statement 156 | func (s *StanLogger) Noticef(format string, v ...interface{}) { 157 | s.executeLogCall(func(log Logger, format string, v ...interface{}) { 158 | log.Noticef(format, v...) 159 | }, format, v...) 160 | } 161 | 162 | // Errorf logs an error 163 | func (s *StanLogger) Errorf(format string, v ...interface{}) { 164 | s.executeLogCall(func(log Logger, format string, v ...interface{}) { 165 | log.Errorf(format, v...) 166 | }, format, v...) 167 | } 168 | 169 | // Fatalf logs a fatal error 170 | func (s *StanLogger) Fatalf(format string, v ...interface{}) { 171 | s.executeLogCall(func(log Logger, format string, v ...interface{}) { 172 | log.Fatalf(format, v...) 173 | }, format, v...) 174 | } 175 | 176 | // Debugf logs a debug statement 177 | func (s *StanLogger) Debugf(format string, v ...interface{}) { 178 | s.executeLogCall(func(log Logger, format string, v ...interface{}) { 179 | // This is running under the protection of StanLogging's lock 180 | if s.debug { 181 | log.Debugf(format, v...) 182 | } 183 | }, format, v...) 184 | } 185 | 186 | // Tracef logs a trace statement 187 | func (s *StanLogger) Tracef(format string, v ...interface{}) { 188 | s.executeLogCall(func(logger Logger, format string, v ...interface{}) { 189 | if s.trace { 190 | logger.Tracef(format, v...) 191 | } 192 | }, format, v...) 193 | } 194 | 195 | // Warnf logs a warning statement 196 | func (s *StanLogger) Warnf(format string, v ...interface{}) { 197 | s.executeLogCall(func(logger Logger, format string, v ...interface{}) { 198 | logger.Warnf(format, v...) 199 | }, format, v...) 200 | } 201 | 202 | func (s *StanLogger) executeLogCall(f func(logger Logger, format string, v ...interface{}), format string, args ...interface{}) { 203 | s.mu.Lock() 204 | if s.log == nil { 205 | s.mu.Unlock() 206 | return 207 | } 208 | f(s.log, LogPrefix+format, args...) 209 | s.mu.Unlock() 210 | } 211 | -------------------------------------------------------------------------------- /logger/logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package logger 15 | 16 | import ( 17 | "errors" 18 | "flag" 19 | "fmt" 20 | "os" 21 | "path/filepath" 22 | "strings" 23 | "testing" 24 | 25 | natsdLogger "github.com/nats-io/nats-server/v2/logger" 26 | ) 27 | 28 | func TestMain(m *testing.M) { 29 | // This one is added here so that if we want to disable sql for stores tests 30 | // we can use the same param for all packages as in "go test -v ./... -sql=false" 31 | flag.Bool("sql", false, "Not used for logger tests") 32 | os.Exit(m.Run()) 33 | } 34 | 35 | type dummyLogger struct { 36 | msg string 37 | } 38 | 39 | func (d *dummyLogger) Noticef(format string, args ...interface{}) { 40 | d.msg = fmt.Sprintf(format, args...) 41 | } 42 | 43 | func (d *dummyLogger) Debugf(format string, args ...interface{}) { 44 | d.msg = fmt.Sprintf(format, args...) 45 | } 46 | 47 | func (d *dummyLogger) Tracef(format string, args ...interface{}) { 48 | d.msg = fmt.Sprintf(format, args...) 49 | } 50 | 51 | func (d *dummyLogger) Errorf(format string, args ...interface{}) { 52 | d.msg = fmt.Sprintf(format, args...) 53 | } 54 | 55 | func (d *dummyLogger) Fatalf(format string, args ...interface{}) { 56 | d.msg = fmt.Sprintf(format, args...) 57 | } 58 | 59 | func (d *dummyLogger) Warnf(format string, args ...interface{}) { 60 | d.msg = fmt.Sprintf(format, args...) 61 | } 62 | 63 | func (d *dummyLogger) Close() error { 64 | return errors.New("dummy error") 65 | } 66 | 67 | func (d *dummyLogger) Reset() { 68 | d.msg = "" 69 | } 70 | 71 | func TestLogger(t *testing.T) { 72 | logger := NewStanLogger() 73 | 74 | check := func(log Logger, logtime, debug, trace bool, logfile string) { 75 | logger.mu.RLock() 76 | l, ltime, dbg, trc, lfile := logger.log, logger.ltime, logger.debug, logger.trace, logger.lfile 77 | logger.mu.RUnlock() 78 | if log != l { 79 | t.Fatalf("Expected log to be %v, got %v", log, l) 80 | } 81 | if logtime != ltime { 82 | t.Fatalf("Expected ltime to be %v, got %v", logtime, ltime) 83 | } 84 | if debug != dbg { 85 | t.Fatalf("Expected debug to be %v, got %v", debug, dbg) 86 | } 87 | if trace != trc { 88 | t.Fatalf("Expected trace to be %v, got %v", trace, trc) 89 | } 90 | if logfile != lfile { 91 | t.Fatalf("Expected lfile to be %v, got %v", logfile, lfile) 92 | } 93 | } 94 | check(nil, false, false, false, "") 95 | // This should not produce any logging, but should not crash 96 | logger.Noticef("Should not crash") 97 | 98 | logger.SetLogger(nil, false, false, false, "") 99 | check(nil, false, false, false, "") 100 | 101 | logger.SetLogger(nil, true, false, false, "") 102 | check(nil, true, false, false, "") 103 | 104 | logger.SetLogger(nil, false, true, false, "") 105 | check(nil, false, true, false, "") 106 | 107 | logger.SetLogger(nil, false, false, true, "") 108 | check(nil, false, false, true, "") 109 | 110 | logger.SetLogger(nil, false, false, false, "test.log") 111 | check(nil, false, false, false, "test.log") 112 | 113 | logger.SetLogger(nil, true, false, false, "test.log") 114 | check(nil, true, false, false, "test.log") 115 | 116 | logger.SetLogger(nil, true, true, false, "test.log") 117 | check(nil, true, true, false, "test.log") 118 | 119 | logger.SetLogger(nil, true, true, true, "test.log") 120 | check(nil, true, true, true, "test.log") 121 | 122 | dl := &dummyLogger{} 123 | logger.SetLogger(dl, false, false, false, "") 124 | check(dl, false, false, false, "") 125 | 126 | ls := logger.GetLogger() 127 | if dl != ls { 128 | t.Fatalf("Expected GetLogger to return %v, got %v", dl, ls) 129 | } 130 | 131 | checkLogger := func(output string) { 132 | prefixRemoved := strings.TrimPrefix(dl.msg, LogPrefix) 133 | if output != prefixRemoved { 134 | t.Fatalf("Unexpected logger message: \"%v\" != \"%v\"", prefixRemoved, output) 135 | } 136 | dl.Reset() 137 | } 138 | 139 | // write to our logger and check values 140 | logger.Noticef("foo") 141 | checkLogger("foo") 142 | 143 | logger.Errorf("foo") 144 | checkLogger("foo") 145 | 146 | logger.Fatalf("foo") 147 | checkLogger("foo") 148 | 149 | // debug is NOT set, value should be empty. 150 | logger.Debugf("foo") 151 | checkLogger("") 152 | 153 | // trace is NOT set, value should be empty. 154 | logger.Tracef("foo") 155 | checkLogger("") 156 | 157 | // enable debug and trace 158 | logger.SetLogger(dl, false, true, true, "") 159 | 160 | // Debug is set so we should have the value 161 | logger.Debugf("foo") 162 | checkLogger("foo") 163 | 164 | // Trace is set so we should have the value 165 | logger.Tracef("foo") 166 | checkLogger("foo") 167 | 168 | // Trying to re-open should log that this is not a filelog based 169 | logger.ReopenLogFile() 170 | checkLogger("File log re-open ignored, not a file logger") 171 | 172 | // Set a filename but still use the dummy logger 173 | logger.SetLogger(dl, true, true, true, "dummy.log") 174 | // Try to re-open, this should produce an error 175 | logger.ReopenLogFile() 176 | checkLogger("Unable to close logger: dummy error") 177 | 178 | // Switch to file log 179 | tmpDir, err := os.MkdirTemp("", "") 180 | if err != nil { 181 | t.Fatalf("Unable to create temp dir: %v", err) 182 | } 183 | defer os.RemoveAll(tmpDir) 184 | fname := filepath.Join(tmpDir, "test.log") 185 | fl := natsdLogger.NewFileLogger(fname, true, true, true, true) 186 | logger.SetLogger(fl, true, true, true, fname) 187 | logger.SetFileSizeLimit(1000) 188 | // Reopen and check file content 189 | logger.ReopenLogFile() 190 | buf, err := os.ReadFile(fname) 191 | if err != nil { 192 | t.Fatalf("Error reading file: %v", err) 193 | } 194 | expectedStr := "File log re-opened" 195 | if !strings.Contains(string(buf), expectedStr) { 196 | t.Fatalf("Expected log to contain %q, got %q", expectedStr, string(buf)) 197 | } 198 | // Make sure that the file size limit was applied to the new file 199 | notice := "This is some notice..." 200 | for i := 0; i < 100; i++ { 201 | logger.Noticef(notice) 202 | } 203 | files, err := os.ReadDir(tmpDir) 204 | if err != nil { 205 | t.Fatalf("Unable to read temp dir: %v", err) 206 | } 207 | if len(files) == 1 { 208 | t.Fatalf("Size limit was not applied") 209 | } 210 | logger.Close() 211 | if err := os.Remove(fname); err != nil { 212 | t.Fatalf("Unable to remove log file: %v", err) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /nats-streaming-server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //go:generate protoc -I=. -I=$GOPATH/src --gofast_out=. ./spb/protocol.proto 15 | 16 | package main 17 | 18 | import ( 19 | "flag" 20 | "fmt" 21 | "os" 22 | "runtime" 23 | 24 | natsd "github.com/nats-io/nats-server/v2/server" 25 | stand "github.com/nats-io/nats-streaming-server/server" 26 | 27 | _ "github.com/go-sql-driver/mysql" // mysql driver 28 | _ "github.com/lib/pq" // postgres driver 29 | _ "github.com/nats-io/nats-streaming-server/stores/pqdeadlines" // wrapper for postgres that gives read/write deadlines 30 | ) 31 | 32 | var usageStr = ` 33 | Usage: nats-streaming-server [options] 34 | 35 | Streaming Server Options: 36 | -cid, --cluster_id Cluster ID (default: test-cluster) 37 | -st, --store Store type: MEMORY|FILE|SQL (default: MEMORY) 38 | --dir For FILE store type, this is the root directory 39 | -mc, --max_channels Max number of channels (0 for unlimited) 40 | -msu, --max_subs Max number of subscriptions per channel (0 for unlimited) 41 | -mm, --max_msgs Max number of messages per channel (0 for unlimited) 42 | -mb, --max_bytes Max messages total size per channel (0 for unlimited) 43 | -ma, --max_age Max duration a message can be stored ("0s" for unlimited) 44 | -mi, --max_inactivity Max inactivity (no new message, no subscription) after which a channel can be garbage collected (0 for unlimited) 45 | -ns, --nats_server Connect to this external NATS Server URL (embedded otherwise) 46 | -sc, --stan_config Streaming server configuration file 47 | -hbi, --hb_interval Interval at which server sends heartbeat to a client 48 | -hbt, --hb_timeout How long server waits for a heartbeat response 49 | -hbf, --hb_fail_count Number of failed heartbeats before server closes the client connection 50 | --ft_group Name of the FT Group. A group can be 2 or more servers with a single active server and all sharing the same datastore 51 | -sl, --signal [=] Send signal to nats-streaming-server process (stop, quit, reopen, reload - only for embedded NATS Server) 52 | --encrypt Specify if server should use encryption at rest 53 | --encryption_cipher Cipher to use for encryption. Currently support AES and CHAHA (ChaChaPoly). Defaults to AES 54 | --encryption_key Encryption Key. It is recommended to specify it through the NATS_STREAMING_ENCRYPTION_KEY environment variable instead 55 | --replace_durable Replace the existing durable subscription instead of reporting a duplicate durable error 56 | 57 | Streaming Server Clustering Options: 58 | --clustered Run the server in a clustered configuration (default: false) 59 | --cluster_node_id ID of the node within the cluster if there is no stored ID (default: random UUID) 60 | --cluster_bootstrap Bootstrap the cluster if there is no existing state by electing self as leader (default: false) 61 | --cluster_peers Comma separated list of cluster peer node IDs to bootstrap cluster state 62 | --cluster_log_path Directory to store log replication data 63 | --cluster_log_cache_size Number of log entries to cache in memory to reduce disk IO (default: 512) 64 | --cluster_log_snapshots Number of log snapshots to retain (default: 2) 65 | --cluster_trailing_logs Number of log entries to leave after a snapshot and compaction 66 | --cluster_sync Do a file sync after every write to the replication log and message store 67 | --cluster_raft_logging Enable logging from the Raft library (disabled by default) 68 | --cluster_allow_add_remove_node Enable the ability to send NATS requests to the leader to add/remove cluster nodes 69 | 70 | Streaming Server File Store Options: 71 | --file_compact_enabled Enable file compaction 72 | --file_compact_frag File fragmentation threshold for compaction 73 | --file_compact_interval Minimum interval (in seconds) between file compactions 74 | --file_compact_min_size Minimum file size for compaction 75 | --file_buffer_size File buffer size (in bytes) 76 | --file_crc Enable file CRC-32 checksum 77 | --file_crc_poly Polynomial used to make the table used for CRC-32 checksum 78 | --file_sync Enable File.Sync on Flush 79 | --file_slice_max_msgs Maximum number of messages per file slice (subject to channel limits) 80 | --file_slice_max_bytes Maximum file slice size - including index file (subject to channel limits) 81 | --file_slice_max_age Maximum file slice duration starting when the first message is stored (subject to channel limits) 82 | --file_slice_archive_script Path to script to use if you want to archive a file slice being removed 83 | --file_fds_limit Store will try to use no more file descriptors than this given limit 84 | --file_parallel_recovery On startup, number of channels that can be recovered in parallel 85 | --file_truncate_bad_eof Truncate files for which there is an unexpected EOF on recovery, dataloss may occur 86 | --file_read_buffer_size Size of messages read ahead buffer (0 to disable) 87 | --file_auto_sync Interval at which the store should be automatically flushed and sync'ed on disk (<= 0 to disable) 88 | 89 | Streaming Server SQL Store Options: 90 | --sql_driver Name of the SQL Driver ("mysql" or "postgres") 91 | --sql_source Datasource used when opening an SQL connection to the database 92 | --sql_no_caching Enable/Disable caching for improved performance 93 | --sql_max_open_conns Maximum number of opened connections to the database 94 | --sql_bulk_insert_limit Maximum number of messages stored with a single SQL "INSERT" statement 95 | 96 | Streaming Server TLS Options: 97 | -secure Use a TLS connection to the NATS server without 98 | verification; weaker than specifying certificates. 99 | -tls_client_key Client key for the streaming server 100 | -tls_client_cert Client certificate for the streaming server 101 | -tls_client_cacert Client certificate CA for the streaming server 102 | 103 | Streaming Server Logging Options: 104 | -SD, --stan_debug= Enable STAN debugging output 105 | -SV, --stan_trace= Trace the raw STAN protocol 106 | -SDV Debug and trace STAN 107 | --syslog_name On Windows, when running several servers as a service, use this name for the event source 108 | (See additional NATS logging options below) 109 | 110 | Embedded NATS Server Options: 111 | -a, --addr Bind to host address (default: 0.0.0.0) 112 | -p, --port Use port for clients (default: 4222) 113 | -P, --pid File to store PID 114 | -m, --http_port Use port for http monitoring 115 | -ms,--https_port Use port for https monitoring 116 | -c, --config Configuration file 117 | 118 | Logging Options: 119 | -l, --log File to redirect log output 120 | -T, --logtime= Timestamp log entries (default: true) 121 | -s, --syslog Enable syslog as log method 122 | -r, --remote_syslog Syslog server addr (udp://localhost:514) 123 | -D, --debug= Enable debugging output 124 | -V, --trace= Trace the raw protocol 125 | -DV Debug and trace 126 | 127 | Authorization Options: 128 | --user User required for connections 129 | --pass Password required for connections 130 | --auth Authorization token required for connections 131 | 132 | TLS Options: 133 | --tls= Enable TLS, do not verify clients (default: false) 134 | --tlscert Server certificate file 135 | --tlskey Private key for server certificate 136 | --tlsverify= Enable TLS, verify client certificates 137 | --tlscacert Client certificate CA for verification 138 | 139 | NATS Clustering Options: 140 | --routes Routes to solicit and connect 141 | --cluster Cluster URL for solicited routes 142 | 143 | Common Options: 144 | -h, --help Show this message 145 | -v, --version Show version 146 | --help_tls TLS help. 147 | ` 148 | 149 | // usage will print out the flag options for the server. 150 | func usage() { 151 | fmt.Printf("%s\n", usageStr) 152 | os.Exit(0) 153 | } 154 | 155 | func main() { 156 | // Parse flags 157 | sOpts, nOpts := parseFlags() 158 | // Force the streaming server to setup its own signal handler 159 | sOpts.HandleSignals = true 160 | // override the NoSigs for NATS since Streaming has its own signal handler 161 | nOpts.NoSigs = true 162 | // Without this option set to true, the logger is not configured. 163 | sOpts.EnableLogging = true 164 | // This will invoke RunServerWithOpts but on Windows, may run it as a service. 165 | if _, err := stand.Run(sOpts, nOpts); err != nil { 166 | fmt.Println(err) 167 | os.Exit(1) 168 | } 169 | runtime.Goexit() 170 | } 171 | 172 | func parseFlags() (*stand.Options, *natsd.Options) { 173 | fs := flag.NewFlagSet("streaming", flag.ExitOnError) 174 | fs.Usage = usage 175 | 176 | stanOpts, natsOpts, err := stand.ConfigureOptions(fs, os.Args[1:], 177 | func() { 178 | fmt.Printf("nats-streaming-server version %s, ", stand.VERSION) 179 | natsd.PrintServerAndExit() 180 | }, 181 | fs.Usage, 182 | natsd.PrintTLSHelpAndDie) 183 | if err != nil { 184 | natsd.PrintAndDie(err.Error()) 185 | } 186 | return stanOpts, natsOpts 187 | } 188 | -------------------------------------------------------------------------------- /scripts/cov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # Run from directory above via ./scripts/cov.sh 3 | 4 | export GO111MODULE="on" 5 | 6 | go install github.com/mattn/goveralls@latest 7 | go install github.com/wadey/gocovmerge@latest 8 | 9 | rm -rf ./cov 10 | mkdir cov 11 | go test -v -failfast -covermode=atomic -coverprofile=./cov/server.out -coverpkg=./server,./stores,./util ./server 12 | go test -v -failfast -covermode=atomic -coverprofile=./cov/server2.out -coverpkg=./server,./stores,./util -run=TestPersistent ./server -encrypt 13 | go test -v -failfast -covermode=atomic -coverprofile=./cov/logger.out ./logger 14 | go test -v -failfast -covermode=atomic -coverprofile=./cov/stores1.out ./stores 15 | go test -v -failfast -covermode=atomic -coverprofile=./cov/stores2.out -run=TestCS/FILE ./stores -fs_no_buffer 16 | go test -v -failfast -covermode=atomic -coverprofile=./cov/stores3.out -run=TestCS/FILE ./stores -fs_set_fds_limit 17 | go test -v -failfast -covermode=atomic -coverprofile=./cov/stores4.out -run=TestCS/FILE ./stores -fs_no_buffer -fs_set_fds_limit 18 | go test -v -failfast -covermode=atomic -coverprofile=./cov/stores5.out -run=TestFS ./stores -fs_no_buffer 19 | go test -v -failfast -covermode=atomic -coverprofile=./cov/stores6.out -run=TestFS ./stores -fs_set_fds_limit 20 | go test -v -failfast -covermode=atomic -coverprofile=./cov/stores7.out -run=TestFS ./stores -fs_no_buffer -fs_set_fds_limit 21 | go test -v -failfast -covermode=atomic -coverprofile=./cov/stores8.out -run=TestCS ./stores -encrypt 22 | go test -v -failfast -covermode=atomic -coverprofile=./cov/util.out ./util 23 | gocovmerge ./cov/*.out > acc.out 24 | rm -rf ./cov 25 | 26 | # Without argument, launch browser results. We are going to push to coveralls only 27 | # from Travis.yml and after success of the build (and result of pushing will not affect 28 | # build result). 29 | if [[ $1 == "" ]]; then 30 | go tool cover -html=acc.out 31 | fi 32 | -------------------------------------------------------------------------------- /scripts/drop_postgres.db.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS ServerInfo; 2 | DROP TABLE IF EXISTS Clients; 3 | DROP TABLE IF EXISTS Channels; 4 | DROP INDEX IF EXISTS Idx_ChannelsName; 5 | DROP TABLE IF EXISTS Messages; 6 | DROP INDEX IF EXISTS Idx_MsgsTimestamp; 7 | DROP TABLE IF EXISTS Subscriptions; 8 | DROP TABLE IF EXISTS SubsPending; 9 | DROP INDEX IF EXISTS Idx_SubsPendingSeq; 10 | DROP TABLE IF EXISTS StoreLock; 11 | -------------------------------------------------------------------------------- /scripts/mysql.db.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS ServerInfo (uniquerow INT DEFAULT 1, id VARCHAR(1024), proto BLOB, version INTEGER, PRIMARY KEY (uniquerow)); 2 | CREATE TABLE IF NOT EXISTS Clients (id VARCHAR(1024), hbinbox TEXT, PRIMARY KEY (id(256))); 3 | CREATE TABLE IF NOT EXISTS Channels (id INTEGER, name VARCHAR(1024) NOT NULL, maxseq BIGINT UNSIGNED DEFAULT 0, maxmsgs INTEGER DEFAULT 0, maxbytes BIGINT DEFAULT 0, maxage BIGINT DEFAULT 0, deleted BOOL DEFAULT FALSE, PRIMARY KEY (id), INDEX Idx_ChannelsName (name(256))); 4 | CREATE TABLE IF NOT EXISTS Messages (id INTEGER, seq BIGINT UNSIGNED, timestamp BIGINT, size INTEGER, data BLOB, CONSTRAINT PK_MsgKey PRIMARY KEY(id, seq), INDEX Idx_MsgsTimestamp (timestamp)); 5 | CREATE TABLE IF NOT EXISTS Subscriptions (id INTEGER, subid BIGINT UNSIGNED, lastsent BIGINT UNSIGNED DEFAULT 0, proto BLOB, deleted BOOL DEFAULT FALSE, CONSTRAINT PK_SubKey PRIMARY KEY(id, subid)); 6 | CREATE TABLE IF NOT EXISTS SubsPending (subid BIGINT UNSIGNED, `row` BIGINT UNSIGNED, seq BIGINT UNSIGNED DEFAULT 0, lastsent BIGINT UNSIGNED DEFAULT 0, pending BLOB, acks BLOB, CONSTRAINT PK_MsgPendingKey PRIMARY KEY(subid, `row`), INDEX Idx_SubsPendingSeq(seq)); 7 | CREATE TABLE IF NOT EXISTS StoreLock (id VARCHAR(30), tick BIGINT UNSIGNED DEFAULT 0); 8 | 9 | # Updates for 0.10.0 10 | ALTER TABLE Clients ADD proto BLOB; 11 | -------------------------------------------------------------------------------- /scripts/postgres.db.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS ServerInfo (uniquerow INTEGER DEFAULT 1, id VARCHAR(1024), proto BYTEA, version INTEGER, PRIMARY KEY (uniquerow)); 2 | CREATE TABLE IF NOT EXISTS Clients (id VARCHAR(1024), hbinbox TEXT, PRIMARY KEY (id)); 3 | CREATE TABLE IF NOT EXISTS Channels (id INTEGER, name VARCHAR(1024) NOT NULL, maxseq BIGINT DEFAULT 0, maxmsgs INTEGER DEFAULT 0, maxbytes BIGINT DEFAULT 0, maxage BIGINT DEFAULT 0, deleted BOOL DEFAULT FALSE, PRIMARY KEY (id)); 4 | CREATE INDEX Idx_ChannelsName ON Channels (name(256)); 5 | CREATE TABLE IF NOT EXISTS Messages (id INTEGER, seq BIGINT, timestamp BIGINT, size INTEGER, data BYTEA, CONSTRAINT PK_MsgKey PRIMARY KEY(id, seq)); 6 | CREATE INDEX Idx_MsgsTimestamp ON Messages (timestamp); 7 | CREATE TABLE IF NOT EXISTS Subscriptions (id INTEGER, subid BIGINT, lastsent BIGINT DEFAULT 0, proto BYTEA, deleted BOOL DEFAULT FALSE, CONSTRAINT PK_SubKey PRIMARY KEY(id, subid)); 8 | CREATE TABLE IF NOT EXISTS SubsPending (subid BIGINT, row BIGINT, seq BIGINT DEFAULT 0, lastsent BIGINT DEFAULT 0, pending BYTEA, acks BYTEA, CONSTRAINT PK_MsgPendingKey PRIMARY KEY(subid, row)); 9 | CREATE INDEX Idx_SubsPendingSeq ON SubsPending (seq); 10 | CREATE TABLE IF NOT EXISTS StoreLock (id VARCHAR(30), tick BIGINT DEFAULT 0); 11 | 12 | -- Updates for 0.10.0 13 | ALTER TABLE Clients ADD proto BYTEA; 14 | -------------------------------------------------------------------------------- /server/bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package server 15 | 16 | import ( 17 | "os" 18 | "sync/atomic" 19 | "testing" 20 | "time" 21 | 22 | "github.com/nats-io/nats-streaming-server/stores" 23 | "github.com/nats-io/nats-streaming-server/test" 24 | "github.com/nats-io/stan.go" 25 | ) 26 | 27 | func benchCleanupDatastore(b *testing.B, dir string) { 28 | switch benchStoreType { 29 | case stores.TypeFile: 30 | if err := os.RemoveAll(dir); err != nil { 31 | stackFatalf(b, "Error cleaning up datastore: %v", err) 32 | } 33 | case stores.TypeSQL: 34 | test.CleanupSQLDatastore(b, testSQLDriver, testSQLSource) 35 | } 36 | } 37 | 38 | func benchRunServer(b *testing.B) *StanServer { 39 | opts := GetDefaultOptions() 40 | opts.Debug = false 41 | opts.Trace = false 42 | opts.StoreType = benchStoreType 43 | switch benchStoreType { 44 | case stores.TypeFile: 45 | opts.FilestoreDir = defaultDataStore 46 | case stores.TypeSQL: 47 | opts.SQLStoreOpts.Driver = testSQLDriver 48 | opts.SQLStoreOpts.Source = testSQLSource 49 | } 50 | s, err := RunServerWithOpts(opts, nil) 51 | if err != nil { 52 | b.Fatalf("Unable to start server: %v", err) 53 | } 54 | return s 55 | } 56 | 57 | func BenchmarkPublish(b *testing.B) { 58 | b.StopTimer() 59 | 60 | benchCleanupDatastore(b, defaultDataStore) 61 | defer benchCleanupDatastore(b, defaultDataStore) 62 | 63 | s := benchRunServer(b) 64 | defer s.Shutdown() 65 | 66 | sc := NewDefaultConnection(b) 67 | defer sc.Close() 68 | 69 | hw := []byte("Hello World") 70 | 71 | b.StartTimer() 72 | 73 | for i := 0; i < b.N; i++ { 74 | if err := sc.Publish("foo", hw); err != nil { 75 | b.Fatalf("Got error on publish: %v\n", err) 76 | } 77 | } 78 | 79 | b.StopTimer() 80 | } 81 | 82 | func BenchmarkPublishAsync(b *testing.B) { 83 | b.StopTimer() 84 | 85 | benchCleanupDatastore(b, defaultDataStore) 86 | defer benchCleanupDatastore(b, defaultDataStore) 87 | 88 | s := benchRunServer(b) 89 | defer s.Shutdown() 90 | 91 | sc := NewDefaultConnection(b) 92 | defer sc.Close() 93 | 94 | hw := []byte("Hello World") 95 | 96 | ch := make(chan bool) 97 | received := int32(0) 98 | 99 | ah := func(guid string, err error) { 100 | if err != nil { 101 | b.Fatalf("Received an error in ack callback: %v\n", err) 102 | } 103 | if nr := atomic.AddInt32(&received, 1); nr >= int32(b.N) { 104 | ch <- true 105 | } 106 | } 107 | b.StartTimer() 108 | 109 | for i := 0; i < b.N; i++ { 110 | if _, err := sc.PublishAsync("foo", hw, ah); err != nil { 111 | b.Fatalf("Error from PublishAsync: %v\n", err) 112 | } 113 | } 114 | 115 | err := WaitTime(ch, 10*time.Second) 116 | 117 | b.StopTimer() 118 | 119 | if err != nil { 120 | b.Fatal("Timed out waiting for ack messages") 121 | } else if atomic.LoadInt32(&received) != int32(b.N) { 122 | b.Fatalf("Received: %d", received) 123 | } 124 | } 125 | 126 | func BenchmarkSubscribe(b *testing.B) { 127 | b.StopTimer() 128 | 129 | benchCleanupDatastore(b, defaultDataStore) 130 | defer benchCleanupDatastore(b, defaultDataStore) 131 | 132 | s := benchRunServer(b) 133 | defer s.Shutdown() 134 | 135 | sc := NewDefaultConnection(b) 136 | defer sc.Close() 137 | 138 | hw := []byte("Hello World") 139 | pch := make(chan bool) 140 | 141 | // Queue up all the messages. Keep this outside of the timing. 142 | for i := 0; i < b.N; i++ { 143 | if i == b.N-1 { 144 | // last one 145 | sc.PublishAsync("foo", hw, func(lguid string, err error) { 146 | if err != nil { 147 | b.Fatalf("Got an error from ack handler, %v", err) 148 | } 149 | pch <- true 150 | }) 151 | } else { 152 | sc.PublishAsync("foo", hw, nil) 153 | } 154 | } 155 | 156 | // Wait for published to finish 157 | if err := WaitTime(pch, 10*time.Second); err != nil { 158 | b.Fatalf("Error waiting for publish to finish\n") 159 | } 160 | 161 | ch := make(chan bool) 162 | received := int32(0) 163 | 164 | b.StartTimer() 165 | 166 | sc.Subscribe("foo", func(m *stan.Msg) { 167 | if nr := atomic.AddInt32(&received, 1); nr >= int32(b.N) { 168 | ch <- true 169 | } 170 | }, stan.DeliverAllAvailable()) 171 | 172 | err := WaitTime(ch, 10*time.Second) 173 | 174 | b.StopTimer() 175 | 176 | nr := atomic.LoadInt32(&received) 177 | if err != nil { 178 | b.Fatalf("Timed out waiting for messages, received only %d of %d\n", nr, b.N) 179 | } else if nr != int32(b.N) { 180 | b.Fatalf("Only Received: %d of %d", received, b.N) 181 | } 182 | } 183 | 184 | func BenchmarkQueueSubscribe(b *testing.B) { 185 | b.StopTimer() 186 | 187 | benchCleanupDatastore(b, defaultDataStore) 188 | defer benchCleanupDatastore(b, defaultDataStore) 189 | 190 | s := benchRunServer(b) 191 | defer s.Shutdown() 192 | 193 | sc := NewDefaultConnection(b) 194 | defer sc.Close() 195 | 196 | hw := []byte("Hello World") 197 | pch := make(chan bool) 198 | 199 | // Queue up all the messages. Keep this outside of the timing. 200 | for i := 0; i < b.N; i++ { 201 | if i == b.N-1 { 202 | // last one 203 | sc.PublishAsync("foo", hw, func(lguid string, err error) { 204 | if err != nil { 205 | b.Fatalf("Got an error from ack handler, %v", err) 206 | } 207 | pch <- true 208 | }) 209 | } else { 210 | sc.PublishAsync("foo", hw, nil) 211 | } 212 | } 213 | 214 | // Wait for published to finish 215 | if err := WaitTime(pch, 10*time.Second); err != nil { 216 | b.Fatalf("Error waiting for publish to finish\n") 217 | } 218 | 219 | ch := make(chan bool) 220 | received := int32(0) 221 | 222 | mcb := func(m *stan.Msg) { 223 | if nr := atomic.AddInt32(&received, 1); nr >= int32(b.N) { 224 | ch <- true 225 | } 226 | } 227 | 228 | b.StartTimer() 229 | 230 | sc.QueueSubscribe("foo", "bar", mcb, stan.DeliverAllAvailable()) 231 | sc.QueueSubscribe("foo", "bar", mcb, stan.DeliverAllAvailable()) 232 | sc.QueueSubscribe("foo", "bar", mcb, stan.DeliverAllAvailable()) 233 | sc.QueueSubscribe("foo", "bar", mcb, stan.DeliverAllAvailable()) 234 | 235 | err := WaitTime(ch, 20*time.Second) 236 | 237 | b.StopTimer() 238 | 239 | nr := atomic.LoadInt32(&received) 240 | if err != nil { 241 | b.Fatalf("Timed out waiting for messages, received only %d of %d\n", nr, b.N) 242 | } else if nr != int32(b.N) { 243 | b.Fatalf("Only Received: %d of %d", received, b.N) 244 | } 245 | } 246 | 247 | func BenchmarkPublishSubscribe(b *testing.B) { 248 | b.StopTimer() 249 | 250 | benchCleanupDatastore(b, defaultDataStore) 251 | defer benchCleanupDatastore(b, defaultDataStore) 252 | 253 | s := benchRunServer(b) 254 | defer s.Shutdown() 255 | 256 | sc := NewDefaultConnection(b) 257 | defer sc.Close() 258 | 259 | hw := []byte("Hello World") 260 | 261 | ch := make(chan bool) 262 | received := int32(0) 263 | 264 | // Subscribe callback, counts msgs received. 265 | _, err := sc.Subscribe("foo", func(m *stan.Msg) { 266 | if nr := atomic.AddInt32(&received, 1); nr >= int32(b.N) { 267 | ch <- true 268 | } 269 | }, stan.DeliverAllAvailable()) 270 | 271 | if err != nil { 272 | b.Fatalf("Error subscribing, %v", err) 273 | } 274 | 275 | b.StartTimer() 276 | 277 | for i := 0; i < b.N; i++ { 278 | _, err := sc.PublishAsync("foo", hw, func(guid string, err error) { 279 | if err != nil { 280 | b.Fatalf("Received an error in publish ack callback: %v\n", err) 281 | } 282 | }) 283 | if err != nil { 284 | b.Fatalf("Error publishing %v\n", err) 285 | } 286 | } 287 | 288 | err = WaitTime(ch, 30*time.Second) 289 | 290 | b.StopTimer() 291 | 292 | nr := atomic.LoadInt32(&received) 293 | if err != nil { 294 | b.Fatalf("Timed out waiting for messages, received only %d of %d\n", nr, b.N) 295 | } else if nr != int32(b.N) { 296 | b.Fatalf("Only Received: %d of %d", received, b.N) 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /server/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2018 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package server 15 | 16 | import ( 17 | "fmt" 18 | "sync" 19 | "time" 20 | 21 | "github.com/nats-io/nats-streaming-server/spb" 22 | "github.com/nats-io/nats-streaming-server/stores" 23 | ) 24 | 25 | const ( 26 | maxKnownInvalidConns = 256 27 | pruneKnownInvalidConns = 32 28 | ) 29 | 30 | // This is a proxy to the store interface. 31 | type clientStore struct { 32 | sync.RWMutex 33 | clients map[string]*client 34 | connIDs map[string]*client 35 | waitOnRegister map[string]chan struct{} 36 | knownInvalid map[string]struct{} 37 | store stores.Store 38 | } 39 | 40 | // client has information needed by the server. A client is also 41 | // stored in a stores.Client object (which contains ID and HbInbox). 42 | type client struct { 43 | sync.RWMutex 44 | info *stores.Client 45 | hbt *time.Timer 46 | fhb int 47 | subs []*subState 48 | } 49 | 50 | // newClientStore creates a new clientStore instance using `store` as the backing storage. 51 | func newClientStore(store stores.Store) *clientStore { 52 | return &clientStore{ 53 | clients: make(map[string]*client), 54 | connIDs: make(map[string]*client), 55 | knownInvalid: make(map[string]struct{}), 56 | store: store, 57 | } 58 | } 59 | 60 | // getSubsCopy returns a copy of the client's subscribers array. 61 | // At least Read-lock must be held by the caller. 62 | func (c *client) getSubsCopy() []*subState { 63 | subs := make([]*subState, len(c.subs)) 64 | copy(subs, c.subs) 65 | return subs 66 | } 67 | 68 | func getKnownInvalidKey(ID string, connID []byte) string { 69 | // The character `:` is not allowed for a client ID, 70 | // so we don't even care if connID is present or not, 71 | // we create the key as clientID+":"(+connID). 72 | return fmt.Sprintf("%s:%s", ID, connID) 73 | } 74 | 75 | // Register a new client. Returns ErrInvalidClient if client is already registered. 76 | func (cs *clientStore) register(info *spb.ClientInfo) (*client, error) { 77 | cs.Lock() 78 | defer cs.Unlock() 79 | c := cs.clients[info.ID] 80 | if c != nil { 81 | return nil, ErrInvalidClient 82 | } 83 | sc, err := cs.store.AddClient(info) 84 | if err != nil { 85 | return nil, err 86 | } 87 | c = &client{info: sc, subs: make([]*subState, 0, 4)} 88 | cs.clients[c.info.ID] = c 89 | if len(c.info.ConnID) > 0 { 90 | cs.connIDs[string(c.info.ConnID)] = c 91 | } 92 | delete(cs.knownInvalid, getKnownInvalidKey(info.ID, info.ConnID)) 93 | if cs.waitOnRegister != nil { 94 | ch := cs.waitOnRegister[c.info.ID] 95 | if ch != nil { 96 | ch <- struct{}{} 97 | delete(cs.waitOnRegister, c.info.ID) 98 | } 99 | } 100 | return c, nil 101 | } 102 | 103 | // Unregister a client. 104 | func (cs *clientStore) unregister(ID string) (*client, error) { 105 | cs.Lock() 106 | defer cs.Unlock() 107 | c := cs.clients[ID] 108 | if c == nil { 109 | return nil, nil 110 | } 111 | c.Lock() 112 | if c.hbt != nil { 113 | c.hbt.Stop() 114 | c.hbt = nil 115 | } 116 | connID := c.info.ConnID 117 | c.Unlock() 118 | delete(cs.clients, ID) 119 | if len(connID) > 0 { 120 | delete(cs.connIDs, string(connID)) 121 | } 122 | if cs.waitOnRegister != nil { 123 | delete(cs.waitOnRegister, ID) 124 | } 125 | err := cs.store.DeleteClient(ID) 126 | return c, err 127 | } 128 | 129 | // IsValid returns true if the client is registered, false otherwise. 130 | func (cs *clientStore) isValid(ID string, connID []byte) bool { 131 | cs.RLock() 132 | valid := cs.lookupByConnIDOrID(ID, connID) != nil 133 | cs.RUnlock() 134 | return valid 135 | } 136 | 137 | // isValidWithTimeout will return true if the client is registered, 138 | // false if not. 139 | // When the client is not yet registered, this call sets up a go channel 140 | // and waits up to `timeout` for the register() call to send the newly 141 | // registered client to the channel. 142 | // On timeout, this call return false to indicate that the client 143 | // has still not registered. 144 | func (cs *clientStore) isValidWithTimeout(ID string, connID []byte, timeout time.Duration) bool { 145 | cs.Lock() 146 | c := cs.lookupByConnIDOrID(ID, connID) 147 | if c != nil { 148 | cs.Unlock() 149 | return true 150 | } 151 | if cs.knownToBeInvalid(ID, connID) { 152 | cs.Unlock() 153 | return false 154 | } 155 | if cs.waitOnRegister == nil { 156 | cs.waitOnRegister = make(map[string]chan struct{}) 157 | } 158 | ch := make(chan struct{}, 1) 159 | cs.waitOnRegister[ID] = ch 160 | cs.Unlock() 161 | select { 162 | case <-ch: 163 | return true 164 | case <-time.After(timeout): 165 | // We timed out, remove the entry in the map 166 | cs.Lock() 167 | delete(cs.waitOnRegister, ID) 168 | cs.addToKnownInvalid(ID, connID) 169 | cs.Unlock() 170 | return false 171 | } 172 | } 173 | 174 | func (cs *clientStore) addToKnownInvalid(ID string, connID []byte) { 175 | key := getKnownInvalidKey(ID, connID) 176 | cs.knownInvalid[key] = struct{}{} 177 | if len(cs.knownInvalid) >= maxKnownInvalidConns { 178 | r := 0 179 | for id := range cs.knownInvalid { 180 | if id != key { 181 | delete(cs.knownInvalid, id) 182 | if r++; r > pruneKnownInvalidConns { 183 | break 184 | } 185 | } 186 | } 187 | } 188 | } 189 | 190 | func (cs *clientStore) knownToBeInvalid(ID string, connID []byte) bool { 191 | _, invalid := cs.knownInvalid[getKnownInvalidKey(ID, connID)] 192 | return invalid 193 | } 194 | 195 | // Lookup client by ConnID if not nil, otherwise by clientID. 196 | // Assume at least clientStore RLock is held on entry. 197 | func (cs *clientStore) lookupByConnIDOrID(ID string, connID []byte) *client { 198 | var c *client 199 | if len(connID) > 0 { 200 | c = cs.connIDs[string(connID)] 201 | } else { 202 | c = cs.clients[ID] 203 | } 204 | return c 205 | } 206 | 207 | // Lookup a client 208 | func (cs *clientStore) lookup(ID string) *client { 209 | cs.RLock() 210 | c := cs.clients[ID] 211 | cs.RUnlock() 212 | return c 213 | } 214 | 215 | // Lookup a client by connection ID 216 | func (cs *clientStore) lookupByConnID(connID []byte) *client { 217 | cs.RLock() 218 | c := cs.connIDs[string(connID)] 219 | cs.RUnlock() 220 | return c 221 | } 222 | 223 | // GetSubs returns the list of subscriptions for the client identified by ID, 224 | // or nil if such client is not found. 225 | func (cs *clientStore) getSubs(ID string) []*subState { 226 | cs.RLock() 227 | defer cs.RUnlock() 228 | c := cs.clients[ID] 229 | if c == nil { 230 | return nil 231 | } 232 | c.RLock() 233 | subs := c.getSubsCopy() 234 | c.RUnlock() 235 | return subs 236 | } 237 | 238 | // AddSub adds the subscription to the client identified by clientID 239 | // and returns true only if the client has not been unregistered, 240 | // otherwise returns false. 241 | func (cs *clientStore) addSub(ID string, sub *subState) bool { 242 | cs.RLock() 243 | defer cs.RUnlock() 244 | c := cs.clients[ID] 245 | if c == nil { 246 | return false 247 | } 248 | c.Lock() 249 | c.subs = append(c.subs, sub) 250 | c.Unlock() 251 | return true 252 | } 253 | 254 | // RemoveSub removes the subscription from the client identified by clientID 255 | // and returns true only if the client has not been unregistered and that 256 | // the subscription was found, otherwise returns false. 257 | func (cs *clientStore) removeSub(ID string, sub *subState) bool { 258 | cs.RLock() 259 | defer cs.RUnlock() 260 | c := cs.clients[ID] 261 | if c == nil { 262 | return false 263 | } 264 | c.Lock() 265 | var removed bool 266 | c.subs, removed = sub.deleteFromList(c.subs) 267 | c.Unlock() 268 | return removed 269 | } 270 | 271 | // recoverClients recreates the content of the client store based on clients 272 | // information recovered from the Store. 273 | func (cs *clientStore) recoverClients(clients []*stores.Client) { 274 | cs.Lock() 275 | for _, sc := range clients { 276 | client := &client{info: sc, subs: make([]*subState, 0, 4)} 277 | cs.clients[client.info.ID] = client 278 | if len(client.info.ConnID) > 0 { 279 | cs.connIDs[string(client.info.ConnID)] = client 280 | } 281 | } 282 | cs.Unlock() 283 | } 284 | 285 | // setClientHB will lookup the client `ID` and, if present, set the 286 | // client's timer with the given interval and function. 287 | func (cs *clientStore) setClientHB(ID string, interval time.Duration, f func()) { 288 | cs.RLock() 289 | defer cs.RUnlock() 290 | c := cs.clients[ID] 291 | if c == nil { 292 | return 293 | } 294 | c.Lock() 295 | if c.hbt == nil { 296 | c.hbt = time.AfterFunc(interval, f) 297 | } 298 | c.Unlock() 299 | } 300 | 301 | // removeClientHB will stop and remove the client's heartbeat timer, if 302 | // present. 303 | func (cs *clientStore) removeClientHB(c *client) { 304 | if c == nil { 305 | return 306 | } 307 | c.Lock() 308 | if c.hbt != nil { 309 | c.hbt.Stop() 310 | c.hbt = nil 311 | } 312 | c.Unlock() 313 | } 314 | 315 | // getClients returns a snapshot of the registered clients. 316 | // The map itself is a copy (can be iterated safely), but 317 | // the clients objects returned are the one stored in the clientStore. 318 | func (cs *clientStore) getClients() map[string]*client { 319 | cs.RLock() 320 | defer cs.RUnlock() 321 | clients := make(map[string]*client, len(cs.clients)) 322 | for _, c := range cs.clients { 323 | clients[c.info.ID] = c 324 | } 325 | return clients 326 | } 327 | 328 | // count returns the number of registered clients 329 | func (cs *clientStore) count() int { 330 | cs.RLock() 331 | total := len(cs.clients) 332 | cs.RUnlock() 333 | return total 334 | } 335 | -------------------------------------------------------------------------------- /server/ft.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package server 15 | 16 | import ( 17 | "fmt" 18 | "math/rand" 19 | "time" 20 | 21 | "github.com/nats-io/nats-streaming-server/spb" 22 | "github.com/nats-io/nats-streaming-server/stores" 23 | "github.com/nats-io/nats-streaming-server/util" 24 | "github.com/nats-io/nats.go" 25 | ) 26 | 27 | // FT constants 28 | const ( 29 | ftDefaultHBInterval = time.Second 30 | ftDefaultHBMissedInterval = 1250 * time.Millisecond 31 | ) 32 | 33 | var ( 34 | // Some go-routine will panic, which we can't recover in test. 35 | // So the tests will set this to true to be able to test the 36 | // correct behavior. 37 | ftNoPanic bool 38 | // For tests purposes, we may want to pause for the first 39 | // attempt at getting the store lock so that test can 40 | // switch store with a mocked one. 41 | ftPauseBeforeFirstAttempt bool 42 | ftPauseCh = make(chan struct{}) 43 | // This can be changed for tests purposes. 44 | ftHBInterval = ftDefaultHBInterval 45 | ftHBMissedInterval = ftDefaultHBMissedInterval 46 | ) 47 | 48 | func ftReleasePause() { 49 | ftPauseCh <- struct{}{} 50 | } 51 | 52 | // ftStart will return only when this server has become active 53 | // and was able to get the store's exclusive lock. 54 | // This is running in a separate go-routine so if server state 55 | // changes, take care of using the server's lock. 56 | func (s *StanServer) ftStart() (retErr error) { 57 | s.log.Noticef("Starting in standby mode") 58 | // For tests purposes 59 | if ftPauseBeforeFirstAttempt { 60 | <-ftPauseCh 61 | } 62 | print, _ := util.NewBackoffTimeCheck(time.Second, 2, time.Minute) 63 | for { 64 | select { 65 | case <-s.ftQuit: 66 | // we are done 67 | return nil 68 | case <-s.ftHBCh: 69 | // go back to the beginning of the for loop 70 | continue 71 | case <-time.After(s.ftHBMissedInterval): 72 | // try to lock the store 73 | } 74 | locked, err := s.ftGetStoreLock() 75 | if err != nil { 76 | // Log the error, but go back and wait for the next interval and 77 | // try again. It is possible that the error resolves (for instance 78 | // the connection to the database is restored - for SQL stores). 79 | s.log.Errorf("ft: error attempting to get the store lock: %v", err) 80 | continue 81 | } else if locked { 82 | break 83 | } 84 | // Here, we did not get the lock, print and go back to standby. 85 | // Use some backoff for the printing to not fill up the log 86 | if print.Ok() { 87 | s.log.Noticef("ft: unable to get store lock at this time, going back to standby") 88 | } 89 | } 90 | // Capture the time this server activated. It will be used in case several 91 | // servers claim to be active. Not bulletproof since there could be clock 92 | // differences, etc... but when more than one server has acquired the store 93 | // lock it means we are already in trouble, so just trying to minimize the 94 | // possible store corruption... 95 | activationTime := time.Now() 96 | s.log.Noticef("Server is active") 97 | s.startGoRoutine(func() { 98 | s.ftSendHBLoop(activationTime) 99 | }) 100 | // Start the recovery process, etc.. 101 | return s.start(FTActive) 102 | } 103 | 104 | // ftGetStoreLock returns true if the server was able to get the 105 | // exclusive store lock, false othewise, or if there was a fatal error doing so. 106 | func (s *StanServer) ftGetStoreLock() (bool, error) { 107 | // Normally, the store would be set early and is immutable, but some 108 | // FT tests do set a mock store after the server is created, so use 109 | // locking here to avoid race reports. 110 | s.mu.Lock() 111 | store := s.store 112 | s.mu.Unlock() 113 | if ok, err := store.GetExclusiveLock(); !ok || err != nil { 114 | // We got an error not related to locking (could be not supported, 115 | // permissions error, file not reachable, etc..) 116 | if err != nil { 117 | return false, fmt.Errorf("ft: fatal error getting the store lock: %v", err) 118 | } 119 | // If ok is false, it means that we did not get the lock. 120 | return false, nil 121 | } 122 | return true, nil 123 | } 124 | 125 | // ftSendHBLoop is used by an active server to send HB to the FT subject. 126 | // Standby servers receiving those HBs do not attempt to lock the store. 127 | // When they miss HBs, they will. 128 | func (s *StanServer) ftSendHBLoop(activationTime time.Time) { 129 | // Release the wait group on exit 130 | defer s.wg.Done() 131 | 132 | timeAsBytes, _ := activationTime.MarshalBinary() 133 | ftHB := &spb.CtrlMsg{ 134 | MsgType: spb.CtrlMsg_FTHeartbeat, 135 | ServerID: s.serverID, 136 | Data: timeAsBytes, 137 | } 138 | ftHBBytes, _ := ftHB.Marshal() 139 | print, _ := util.NewBackoffTimeCheck(time.Second, 2, time.Minute) 140 | for { 141 | if err := s.ftnc.Publish(s.ftSubject, ftHBBytes); err != nil { 142 | if print.Ok() { 143 | s.log.Errorf("Unable to send FT heartbeat: %v", err) 144 | } 145 | } 146 | startSelect: 147 | select { 148 | case m := <-s.ftHBCh: 149 | hb := spb.CtrlMsg{} 150 | if err := hb.Unmarshal(m.Data); err != nil { 151 | goto startSelect 152 | } 153 | // Ignore our own message 154 | if hb.MsgType != spb.CtrlMsg_FTHeartbeat || hb.ServerID == s.serverID { 155 | goto startSelect 156 | } 157 | // Another server claims to be active 158 | peerActivationTime := time.Time{} 159 | if err := peerActivationTime.UnmarshalBinary(hb.Data); err != nil { 160 | s.log.Errorf("Error decoding activation time: %v", err) 161 | } else { 162 | // Step down if the peer's activation time is earlier than ours. 163 | err := fmt.Errorf("ft: serverID %q claims to be active", hb.ServerID) 164 | if peerActivationTime.Before(activationTime) { 165 | err = fmt.Errorf("%s, aborting", err) 166 | if ftNoPanic { 167 | s.setLastError(err) 168 | return 169 | } 170 | panic(err) 171 | } else { 172 | s.log.Errorf(err.Error()) 173 | } 174 | } 175 | case <-time.After(s.ftHBInterval): 176 | // We'll send the ping at the top of the for loop 177 | case <-s.ftQuit: 178 | return 179 | } 180 | } 181 | } 182 | 183 | // ftSetup checks that all required FT parameters have been specified and 184 | // create the channel required for shutdown. 185 | // Note that FTGroupName has to be set before server invokes this function, 186 | // so this parameter is not checked here. 187 | func (s *StanServer) ftSetup() error { 188 | // Check that store type is ok. So far only support for FileStore 189 | if s.opts.StoreType != stores.TypeFile && s.opts.StoreType != stores.TypeSQL { 190 | return fmt.Errorf("ft: only %v or %v stores supported in FT mode", stores.TypeFile, stores.TypeSQL) 191 | } 192 | // So far, those are not exposed to users, just used in tests. 193 | // Still make sure that the missed HB interval is > than the HB 194 | // interval. 195 | if ftHBMissedInterval < time.Duration(float64(ftHBInterval)*1.1) { 196 | return fmt.Errorf("ft: the missed heartbeat interval needs to be"+ 197 | " at least 10%% of the heartbeat interval (hb=%v missed hb=%v", 198 | ftHBInterval, ftHBMissedInterval) 199 | } 200 | // Set the HB and MissedHB intervals, using a bit of randomness 201 | s.ftHBInterval = ftGetRandomInterval(ftHBInterval) 202 | s.ftHBMissedInterval = ftGetRandomInterval(ftHBMissedInterval) 203 | // Subscribe to FT subject 204 | s.ftSubject = fmt.Sprintf("%s.%s.%s", ftHBPrefix, s.opts.ID, s.opts.FTGroupName) 205 | s.ftHBCh = make(chan *nats.Msg) 206 | sub, err := s.ftnc.Subscribe(s.ftSubject, func(m *nats.Msg) { 207 | // Dropping incoming FT HBs is not crucial, we will then check for 208 | // store lock. 209 | select { 210 | case s.ftHBCh <- m: 211 | default: 212 | } 213 | }) 214 | if err != nil { 215 | return fmt.Errorf("ft: unable to subscribe on ft subject: %v", err) 216 | } 217 | // We don't want to cause possible slow consumer error 218 | sub.SetPendingLimits(-1, -1) 219 | // Create channel to notify FT go routine to quit. 220 | s.ftQuit = make(chan struct{}, 1) 221 | // Set the state as standby initially 222 | s.state = FTStandby 223 | return nil 224 | } 225 | 226 | // ftGetRandomInterval returns a random interval with at most +/- 10% 227 | // of the given interval. 228 | func ftGetRandomInterval(interval time.Duration) time.Duration { 229 | tenPercent := int(float64(interval) * 0.10) 230 | random := time.Duration(rand.Intn(tenPercent)) 231 | if rand.Intn(2) == 1 { 232 | return interval + random 233 | } 234 | return interval - random 235 | } 236 | -------------------------------------------------------------------------------- /server/partitions.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package server 15 | 16 | import ( 17 | "fmt" 18 | "sync" 19 | "time" 20 | 21 | "github.com/nats-io/nats-streaming-server/spb" 22 | "github.com/nats-io/nats-streaming-server/stores" 23 | "github.com/nats-io/nats-streaming-server/util" 24 | "github.com/nats-io/nats.go" 25 | ) 26 | 27 | // Constants related to partitioning 28 | const ( 29 | // Prefix of subject to send list of channels in this server's partition 30 | partitionsPrefix = "_STAN.part" 31 | // Default timeout for a server to wait for replies to its request 32 | partitionsDefaultRequestTimeout = time.Second 33 | // This is the value that is stored in the sublist for a given subject 34 | channelInterest = 1 35 | // Default wait before checking for channels when notified 36 | // that the NATS cluster topology has changed. This gives a chance 37 | // for the new server joining the cluster to send its subscriptions 38 | // list. 39 | partitionsDefaultWaitOnTopologyChange = 500 * time.Millisecond 40 | ) 41 | 42 | // So that we can override in tests 43 | var ( 44 | partitionsRequestTimeout = partitionsDefaultRequestTimeout 45 | partitionsNoPanic = false 46 | partitionsWaitOnChange = partitionsDefaultWaitOnTopologyChange 47 | ) 48 | 49 | type partitions struct { 50 | sync.Mutex 51 | s *StanServer 52 | channels []string 53 | sl *util.Sublist 54 | nc *nats.Conn 55 | sendListSubject string 56 | processChanSub *nats.Subscription 57 | inboxSub *nats.Subscription 58 | isShutdown bool 59 | } 60 | 61 | // Initialize the channels partitions objects and issue the first 62 | // request to check if other servers in the cluster incorrectly have 63 | // any of the channel that this server is supposed to handle. 64 | func (s *StanServer) initPartitions() error { 65 | // The option says that the server should only use the pre-defined channels, 66 | // but none was specified. Don't see the point in continuing... 67 | if len(s.opts.StoreLimits.PerChannel) == 0 { 68 | return ErrNoChannel 69 | } 70 | nc, err := s.createNatsClientConn("pc") 71 | if err != nil { 72 | return err 73 | } 74 | p := &partitions{ 75 | s: s, 76 | nc: nc, 77 | } 78 | // Now that the connection is created, we need to set s.partitioning to cp 79 | // so that server shutdown can properly close this connection. 80 | s.partitions = p 81 | p.createChannelsMapAndSublist(s.opts.StoreLimits.PerChannel) 82 | p.sendListSubject = partitionsPrefix + "." + s.opts.ID 83 | // Use the partitions' own connection for channels list requests 84 | p.processChanSub, err = p.nc.Subscribe(p.sendListSubject, p.processChannelsListRequests) 85 | if err != nil { 86 | return fmt.Errorf("unable to subscribe: %v", err) 87 | } 88 | p.processChanSub.SetPendingLimits(-1, -1) 89 | p.inboxSub, err = p.nc.SubscribeSync(nats.NewInbox()) 90 | if err != nil { 91 | return fmt.Errorf("unable to subscribe: %v", err) 92 | } 93 | p.Lock() 94 | // Set this before the first attempt so we don't miss any notification 95 | // of a change in topology. Since we hold the lock, and even if there 96 | // was a notification happening now, the callback will execute only 97 | // after we are done with the initial check. 98 | nc.SetDiscoveredServersHandler(p.topologyChanged) 99 | // Now send our list and check if any server is complaining 100 | // about having one channel in common. 101 | if err := p.checkChannelsUniqueInCluster(); err != nil { 102 | p.Unlock() 103 | return err 104 | } 105 | p.Unlock() 106 | return nil 107 | } 108 | 109 | // Creates the channels map based on the store's PerChannel map that was given. 110 | func (p *partitions) createChannelsMapAndSublist(storeChannels map[string]*stores.ChannelLimits) { 111 | p.channels = make([]string, 0, len(storeChannels)) 112 | p.sl = util.NewSublist() 113 | for c := range storeChannels { 114 | p.channels = append(p.channels, c) 115 | // When creating the store, we have already checked that channel names 116 | // were valid. So this call cannot fail. 117 | p.sl.Insert(c, channelInterest) 118 | } 119 | } 120 | 121 | // Topology changed. Sends the list of channels. 122 | func (p *partitions) topologyChanged(_ *nats.Conn) { 123 | p.Lock() 124 | defer p.Unlock() 125 | if p.isShutdown { 126 | return 127 | } 128 | // Let's wait before checking (sending the list and waiting for a reply) 129 | // so that the new NATS Server has a chance to send its local 130 | // subscriptions to the rest of the cluster. That will reduce the risk 131 | // of missing the reply from the new server. 132 | time.Sleep(partitionsWaitOnChange) 133 | if err := p.checkChannelsUniqueInCluster(); err != nil { 134 | // If server is started from command line, the Fatalf 135 | // call will cause the process to exit. If the server 136 | // is run programmatically and no logger has been set 137 | // we need to exit with the panic. 138 | p.s.log.Fatalf("Partitioning error: %v", err) 139 | // For tests 140 | if partitionsNoPanic { 141 | p.s.setLastError(err) 142 | return 143 | } 144 | panic(err) 145 | } 146 | } 147 | 148 | // Create the internal subscriptions on the list of channels. 149 | func (p *partitions) initSubscriptions() error { 150 | // NOTE: Use the server's nc connection here, not the partitions' one. 151 | for _, channelName := range p.channels { 152 | pubSubject := fmt.Sprintf("%s.%s", p.s.info.Publish, channelName) 153 | if sub, err := p.s.nc.Subscribe(pubSubject, p.s.processClientPublish); err != nil { 154 | return fmt.Errorf("could not subscribe to publish subject %q, %v", channelName, err) 155 | } else { 156 | sub.SetPendingLimits(-1, -1) 157 | } 158 | } 159 | return nil 160 | } 161 | 162 | // Sends a request to the rest of the cluster and wait a bit for 163 | // responses (we don't know if or how many servers there may be). 164 | // No server lock used since this is called inside RunServerWithOpts(). 165 | func (p *partitions) checkChannelsUniqueInCluster() error { 166 | // We use the subscription on an inbox to get the replies. 167 | // Send our list 168 | if err := util.SendChannelsList(p.channels, p.sendListSubject, p.inboxSub.Subject, p.nc, p.s.serverID); err != nil { 169 | return fmt.Errorf("unable to send channels list: %v", err) 170 | } 171 | // Since we don't know how many servers are out there, keep 172 | // calling NextMsg until we get a timeout 173 | for { 174 | reply, err := p.inboxSub.NextMsg(partitionsRequestTimeout) 175 | if err == nats.ErrTimeout { 176 | return nil 177 | } 178 | if err != nil { 179 | return fmt.Errorf("unable to get partitioning reply: %v", err) 180 | } 181 | resp := spb.CtrlMsg{} 182 | if err := resp.Unmarshal(reply.Data); err != nil { 183 | return fmt.Errorf("unable to decode partitioning response: %v", err) 184 | } 185 | if len(resp.Data) > 0 { 186 | return fmt.Errorf("channel %q causes conflict with channels on server %q", 187 | string(resp.Data), resp.ServerID) 188 | } 189 | } 190 | } 191 | 192 | // Decode the incoming partitioning protocol message. 193 | // It can be an HB, in which case, if it is from a new server 194 | // we send our list to the cluster, or it can be a request 195 | // from another server. If so, we reply to the given inbox 196 | // with either an empty Data field or the name of the first 197 | // channel we have in common. 198 | func (p *partitions) processChannelsListRequests(m *nats.Msg) { 199 | // Message cannot be empty, we are supposed to receive 200 | // a spb.CtrlMsg_Partitioning protocol. We should also 201 | // have a repy subject 202 | if len(m.Data) == 0 || m.Reply == "" { 203 | return 204 | } 205 | req := spb.CtrlMsg{} 206 | if err := req.Unmarshal(m.Data); err != nil { 207 | p.s.log.Errorf("Error processing partitioning request: %v", err) 208 | return 209 | } 210 | // If this is our own request, ignore 211 | if req.ServerID == p.s.serverID { 212 | return 213 | } 214 | channels, err := util.DecodeChannels(req.Data) 215 | if err != nil { 216 | p.s.log.Errorf("Error processing partitioning request: %v", err) 217 | return 218 | } 219 | // Check that we don't have any of these channels defined. 220 | // If we do, send a reply with simply the name of the offending 221 | // channel in reply.Data 222 | reply := spb.CtrlMsg{ 223 | ServerID: p.s.serverID, 224 | MsgType: spb.CtrlMsg_Partitioning, 225 | } 226 | gotError := false 227 | sl := util.NewSublist() 228 | for _, c := range channels { 229 | if r := p.sl.Match(c); len(r) > 0 { 230 | reply.Data = []byte(c) 231 | gotError = true 232 | break 233 | } 234 | sl.Insert(c, channelInterest) 235 | } 236 | if !gotError { 237 | // Go over our channels and check with the other server sublist 238 | for _, c := range p.channels { 239 | if r := sl.Match(c); len(r) > 0 { 240 | reply.Data = []byte(c) 241 | break 242 | } 243 | } 244 | } 245 | replyBytes, _ := reply.Marshal() 246 | // If there is no duplicate, reply.Data will be empty, which means 247 | // that there was no conflict. 248 | if err := p.nc.Publish(m.Reply, replyBytes); err != nil { 249 | p.s.log.Errorf("Error sending reply to partitioning request: %v", err) 250 | } 251 | } 252 | 253 | // Notifies all go-routines used by partitioning code that the 254 | // server is shuting down and closes the internal NATS connection. 255 | func (p *partitions) shutdown() { 256 | p.Lock() 257 | defer p.Unlock() 258 | if p.isShutdown { 259 | return 260 | } 261 | p.isShutdown = true 262 | if p.nc != nil { 263 | p.nc.Close() 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /server/server_req_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2021 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package server 15 | 16 | import ( 17 | "fmt" 18 | "reflect" 19 | "testing" 20 | "time" 21 | 22 | "github.com/nats-io/nats.go" 23 | "github.com/nats-io/stan.go" 24 | "github.com/nats-io/stan.go/pb" 25 | ) 26 | 27 | type response interface { 28 | Unmarshal([]byte) error 29 | } 30 | 31 | func checkServerResponse(nc *nats.Conn, subj string, expectedError error, r response) error { 32 | resp, err := nc.Request(subj, []byte("dummy"), time.Second) 33 | if err != nil { 34 | return fmt.Errorf("Unexpected error on publishing request: %v", err) 35 | } 36 | if err := r.Unmarshal(resp.Data); err != nil { 37 | return fmt.Errorf("Unexpected response object: %v", err) 38 | } 39 | // All our protos have the Error field. 40 | v := reflect.Indirect(reflect.ValueOf(r)) 41 | f := v.FieldByName("Error") 42 | if !f.IsValid() { 43 | return fmt.Errorf("Field Error not found in the response: %v", f) 44 | } 45 | connErr := f.String() 46 | if connErr != expectedError.Error() { 47 | return fmt.Errorf("Expected response to be %q, got %q", expectedError.Error(), connErr) 48 | } 49 | return nil 50 | } 51 | 52 | func TestInvalidRequests(t *testing.T) { 53 | s := runServer(t, clusterName) 54 | defer s.Shutdown() 55 | 56 | // Use a bare NATS connection to send incorrect requests 57 | nc, err := nats.Connect(nats.DefaultURL) 58 | if err != nil { 59 | t.Fatalf("Unexpected error on connect: %v", err) 60 | } 61 | defer nc.Close() 62 | 63 | // Send a dummy message on the STAN connect subject 64 | // Get the connect subject 65 | connSubj := fmt.Sprintf("%s.%s", s.opts.DiscoverPrefix, clusterName) 66 | if err := checkServerResponse(nc, connSubj, ErrInvalidConnReq, 67 | &pb.ConnectResponse{}); err != nil { 68 | t.Fatalf("%v", err) 69 | } 70 | 71 | // Send a dummy message on the STAN publish subject 72 | if err := checkServerResponse(nc, s.info.Publish+".foo", ErrInvalidPubReq, 73 | &pb.PubAck{}); err != nil { 74 | t.Fatalf("%v", err) 75 | } 76 | 77 | // Send a dummy message on the STAN subscription init subject 78 | if err := checkServerResponse(nc, s.info.Subscribe, ErrInvalidSubReq, 79 | &pb.SubscriptionResponse{}); err != nil { 80 | t.Fatalf("%v", err) 81 | } 82 | 83 | // Send a dummy message on the STAN subscription unsub subject 84 | if err := checkServerResponse(nc, s.info.Unsubscribe, ErrInvalidUnsubReq, 85 | &pb.SubscriptionResponse{}); err != nil { 86 | t.Fatalf("%v", err) 87 | } 88 | 89 | // Send a dummy message on the STAN subscription close subject 90 | if err := checkServerResponse(nc, s.info.SubClose, ErrInvalidUnsubReq, 91 | &pb.SubscriptionResponse{}); err != nil { 92 | t.Fatalf("%v", err) 93 | } 94 | 95 | // Send a dummy message on the STAN close subject 96 | if err := checkServerResponse(nc, s.info.Close, ErrInvalidCloseReq, 97 | &pb.CloseResponse{}); err != nil { 98 | t.Fatalf("%v", err) 99 | } 100 | } 101 | 102 | func sendInvalidSubRequest(s *StanServer, nc *nats.Conn, req *pb.SubscriptionRequest, expectedErr error) error { 103 | b, err := req.Marshal() 104 | if err != nil { 105 | return fmt.Errorf("Error during marshal: %v", err) 106 | } 107 | rep, err := nc.Request(s.info.Subscribe, b, time.Second) 108 | if err != nil { 109 | return fmt.Errorf("Unexpected error: %v", err) 110 | } 111 | // Check response 112 | subRep := &pb.SubscriptionResponse{} 113 | subRep.Unmarshal(rep.Data) 114 | 115 | // Expect error 116 | if subRep.Error != expectedErr.Error() { 117 | return fmt.Errorf("Expected error %v, got %v", expectedErr.Error(), subRep.Error) 118 | } 119 | return nil 120 | } 121 | 122 | func TestInvalidSubRequest(t *testing.T) { 123 | s := runServer(t, clusterName) 124 | defer s.Shutdown() 125 | 126 | // Use a bare NATS connection to send incorrect requests 127 | nc, err := nats.Connect(nats.DefaultURL) 128 | if err != nil { 129 | t.Fatalf("Unexpected error on connect: %v", err) 130 | } 131 | defer nc.Close() 132 | 133 | // This test is very dependent on the validity tests performed 134 | // in StanServer.processSubscriptionRequest(). Any change there 135 | // may require changes here. 136 | 137 | // Create empty request 138 | req := &pb.SubscriptionRequest{} 139 | 140 | // We have already tested corrupted SusbcriptionRequests 141 | // (as in Unmarshal errors) in TestInvalidRequests. Here, we check 142 | // validity of request's fields. 143 | 144 | // Send this empty request, clientID is missing 145 | if err := sendInvalidSubRequest(s, nc, req, ErrMissingClient); err != nil { 146 | t.Fatalf("%v", err) 147 | } 148 | 149 | // Set a clientID so we move on to next check 150 | req.ClientID = clientName 151 | 152 | // Test invalid AckWait values 153 | // For these tests, we need to disable testAckWaitIsInMillisecond 154 | testAckWaitIsInMillisecond = false 155 | req.AckWaitInSecs = 0 156 | if err := sendInvalidSubRequest(s, nc, req, ErrInvalidAckWait); err != nil { 157 | t.Fatalf("%v", err) 158 | } 159 | req.AckWaitInSecs = -1 160 | if err := sendInvalidSubRequest(s, nc, req, ErrInvalidAckWait); err != nil { 161 | t.Fatalf("%v", err) 162 | } 163 | testAckWaitIsInMillisecond = true 164 | 165 | // Test invalid MaxInflight values 166 | req.AckWaitInSecs = 1 167 | req.MaxInFlight = 0 168 | if err := sendInvalidSubRequest(s, nc, req, ErrInvalidMaxInflight); err != nil { 169 | t.Fatalf("%v", err) 170 | } 171 | req.MaxInFlight = -1 172 | if err := sendInvalidSubRequest(s, nc, req, ErrInvalidMaxInflight); err != nil { 173 | t.Fatalf("%v", err) 174 | } 175 | 176 | // Test invalid StartPosition values 177 | req.MaxInFlight = 1 178 | req.StartPosition = pb.StartPosition_NewOnly - 1 179 | if err := sendInvalidSubRequest(s, nc, req, ErrInvalidStart); err != nil { 180 | t.Fatalf("%v", err) 181 | } 182 | req.StartPosition = pb.StartPosition_First + 1 183 | if err := sendInvalidSubRequest(s, nc, req, ErrInvalidStart); err != nil { 184 | t.Fatalf("%v", err) 185 | } 186 | 187 | // Test invalid subjects 188 | req.StartPosition = pb.StartPosition_First 189 | req.Subject = "foo*.bar" 190 | if err := sendInvalidSubRequest(s, nc, req, ErrInvalidSubject); err != nil { 191 | t.Fatalf("%v", err) 192 | } 193 | // Other kinds of invalid subject 194 | req.Subject = "foo.bar*" 195 | if err := sendInvalidSubRequest(s, nc, req, ErrInvalidSubject); err != nil { 196 | t.Fatalf("%v", err) 197 | } 198 | req.Subject = "foo.>.*" 199 | if err := sendInvalidSubRequest(s, nc, req, ErrInvalidSubject); err != nil { 200 | t.Fatalf("%v", err) 201 | } 202 | 203 | // Test Queue Group DurableName 204 | sc := NewDefaultConnection(t) 205 | defer sc.Close() 206 | req.Subject = "foo" 207 | req.QGroup = "queue" 208 | req.DurableName = "dur:name" 209 | if err := sendInvalidSubRequest(s, nc, req, ErrInvalidDurName); err != nil { 210 | t.Fatalf("%v", err) 211 | } 212 | sc.Close() 213 | 214 | // Reset those 215 | req.QGroup = "" 216 | req.DurableName = "" 217 | 218 | // Now we should have an error that says that we have an unknown client ID. 219 | if err := sendInvalidSubRequest(s, nc, req, ErrUnknownClient); err != nil { 220 | t.Fatalf("%v", err) 221 | } 222 | 223 | // There should be no client created 224 | checkClients(t, s, 0) 225 | 226 | // But channel "foo" should have been created though 227 | if s.channels.count() == 0 { 228 | t.Fatal("Expected channel foo to have been created") 229 | } 230 | 231 | // Create a durable 232 | sc = NewDefaultConnection(t) 233 | defer sc.Close() 234 | dur, err := sc.Subscribe("foo", func(_ *stan.Msg) {}, stan.DurableName("dur")) 235 | if err != nil { 236 | t.Fatalf("Unexpected error on subscribe: %v", err) 237 | } 238 | // Close durable 239 | if err := dur.Close(); err != nil { 240 | t.Fatalf("Error closing durable: %v", err) 241 | } 242 | // Close client 243 | sc.Close() 244 | // Ensure this is processed 245 | checkClients(t, s, 0) 246 | // Try to update the durable now that client does not exist. 247 | req.ClientID = clientName 248 | req.Subject = "foo" 249 | req.DurableName = "dur" 250 | if err := sendInvalidSubRequest(s, nc, req, ErrUnknownClient); err != nil { 251 | t.Fatalf("%v", err) 252 | } 253 | } 254 | 255 | func sendInvalidUnsubRequest(s *StanServer, nc *nats.Conn, req *pb.UnsubscribeRequest) error { 256 | b, err := req.Marshal() 257 | if err != nil { 258 | return fmt.Errorf("Error during marshal: %v", err) 259 | } 260 | rep, err := nc.Request(s.info.Unsubscribe, b, time.Second) 261 | if err != nil { 262 | return fmt.Errorf("Unexpected error: %v", err) 263 | } 264 | // Check response 265 | subRep := &pb.SubscriptionResponse{} 266 | subRep.Unmarshal(rep.Data) 267 | 268 | // Expect error 269 | if subRep.Error == "" { 270 | return fmt.Errorf("Expected error, got none") 271 | } 272 | return nil 273 | } 274 | 275 | func TestInvalidUnsubRequest(t *testing.T) { 276 | s := runServer(t, clusterName) 277 | defer s.Shutdown() 278 | 279 | // Use a bare NATS connection to send incorrect requests 280 | nc, err := nats.Connect(nats.DefaultURL) 281 | if err != nil { 282 | t.Fatalf("Unexpected error on connect: %v", err) 283 | } 284 | defer nc.Close() 285 | 286 | sc, err := stan.Connect(clusterName, clientName, stan.NatsConn(nc)) 287 | if err != nil { 288 | t.Fatalf("Unexpected error on connect: %v", err) 289 | } 290 | 291 | // Create a valid subscription first 292 | sub, err := sc.Subscribe("foo", func(_ *stan.Msg) {}) 293 | if err != nil { 294 | t.Fatalf("Unexpected error on subscribe: %v", err) 295 | } 296 | 297 | // Verify server state. Client should be created 298 | client := s.clients.lookup(clientName) 299 | if client == nil { 300 | t.Fatal("A client should have been created") 301 | } 302 | subs := checkSubs(t, s, clientName, 1) 303 | 304 | // Create empty request 305 | req := &pb.UnsubscribeRequest{} 306 | 307 | // Send this empty request 308 | if err := sendInvalidUnsubRequest(s, nc, req); err != nil { 309 | t.Fatalf("%v", err) 310 | } 311 | 312 | // Unsubscribe for a subject we did not subscribe to 313 | req.Subject = "bar" 314 | if err := sendInvalidUnsubRequest(s, nc, req); err != nil { 315 | t.Fatalf("%v", err) 316 | } 317 | 318 | // Invalid ack inbox 319 | req.Subject = "foo" 320 | req.ClientID = clientName 321 | req.Inbox = "wrong" 322 | if err := sendInvalidUnsubRequest(s, nc, req); err != nil { 323 | t.Fatalf("%v", err) 324 | } 325 | 326 | // Correct subject, inbox, but invalid ClientID 327 | req.Subject = "foo" 328 | req.Inbox = subs[0].AckInbox 329 | req.ClientID = "wrong" 330 | if err := sendInvalidUnsubRequest(s, nc, req); err != nil { 331 | t.Fatalf("%v", err) 332 | } 333 | 334 | // Valid unsubscribe 335 | if err := sub.Unsubscribe(); err != nil { 336 | t.Fatalf("Unexpected error on unsubscribe: %v\n", err) 337 | } 338 | 339 | time.Sleep(100 * time.Millisecond) 340 | 341 | // Check that sub's has been removed. 342 | checkSubs(t, s, clientName, 0) 343 | } 344 | -------------------------------------------------------------------------------- /server/service.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2022 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //go:build !windows 15 | // +build !windows 16 | 17 | package server 18 | 19 | import ( 20 | natsd "github.com/nats-io/nats-server/v2/server" 21 | ) 22 | 23 | // Run starts the NATS Streaming server. This wrapper function allows Windows to add a 24 | // hook for running NATS Streaming as a service. 25 | func Run(sOpts *Options, nOpts *natsd.Options) (*StanServer, error) { 26 | return RunServerWithOpts(sOpts, nOpts) 27 | } 28 | 29 | // isWindowsService indicates if NATS Streaming is running as a Windows service. 30 | func isWindowsService() bool { 31 | return false 32 | } 33 | -------------------------------------------------------------------------------- /server/service_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //go:build !windows 15 | // +build !windows 16 | 17 | package server 18 | 19 | import ( 20 | "testing" 21 | "time" 22 | ) 23 | 24 | func TestRun(t *testing.T) { 25 | sOpts := GetDefaultOptions() 26 | srvCh := make(chan *StanServer, 1) 27 | errCh := make(chan error, 1) 28 | go func() { 29 | s, err := Run(sOpts, nil) 30 | srvCh <- s 31 | errCh <- err 32 | }() 33 | select { 34 | case err := <-errCh: 35 | if err != nil { 36 | t.Fatalf("Unexpected error: %v", err) 37 | } 38 | case <-time.After(2 * time.Second): 39 | t.Fatalf("Failed to start") 40 | } 41 | 42 | s := <-srvCh 43 | if s == nil { 44 | t.Fatalf("Server is nil") 45 | } 46 | defer s.Shutdown() 47 | if s.State() != Standalone { 48 | t.Fatalf("Unexpected sate: %v", s.State().String()) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /server/service_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package server 15 | 16 | import ( 17 | "fmt" 18 | "os" 19 | "strings" 20 | "sync" 21 | 22 | natsdLogger "github.com/nats-io/nats-server/v2/logger" 23 | natsd "github.com/nats-io/nats-server/v2/server" 24 | "golang.org/x/sys/windows/svc" 25 | "golang.org/x/sys/windows/svc/debug" 26 | "golang.org/x/sys/windows/svc/eventlog" 27 | ) 28 | 29 | const ( 30 | serviceName = "nats-streaming-server" 31 | reopenLogCode = 128 32 | reopenLogCmd = svc.Cmd(reopenLogCode) 33 | acceptReopenLog = svc.Accepted(reopenLogCode) 34 | ) 35 | 36 | // winServiceWrapper implements the svc.Handler interface for implementing 37 | // nats-streaming-server as a Windows service. 38 | type winServiceWrapper struct { 39 | sOpts *Options 40 | nOpts *natsd.Options 41 | srvCh chan *StanServer 42 | errCh chan error 43 | } 44 | 45 | var ( 46 | dockerized = false 47 | sysLogInitLock sync.Mutex 48 | sysLog *eventlog.Log 49 | sysLogName = "NATS-Streaming-Server" 50 | ) 51 | 52 | func init() { 53 | if v, exists := os.LookupEnv("NATS_DOCKERIZED"); exists && v == "1" { 54 | dockerized = true 55 | } 56 | // Set the default event source name. This may be changed when the 57 | // server will configure the logger if a SyslogName option is specified. 58 | natsdLogger.SetSyslogName(sysLogName) 59 | // This is so the gnatsd's signal code works for streaming service 60 | natsd.SetServiceName(serviceName) 61 | } 62 | 63 | // Execute will be called by the package code at the start of 64 | // the service, and the service will exit once Execute completes. 65 | // Inside Execute you must read service change requests from r and 66 | // act accordingly. You must keep service control manager up to date 67 | // about state of your service by writing into s as required. 68 | // args contains service name followed by argument strings passed 69 | // to the service. 70 | // You can provide service exit code in exitCode return parameter, 71 | // with 0 being "no error". You can also indicate if exit code, 72 | // if any, is service specific or not by using svcSpecificEC 73 | // parameter. 74 | func (w *winServiceWrapper) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) { 75 | 76 | status <- svc.Status{State: svc.StartPending} 77 | 78 | if sysLog != nil { 79 | sysLog.Info(1, "Starting NATS Streaming Server...") 80 | } 81 | // Override NoSigs since we are doing signal handling HERE 82 | w.sOpts.HandleSignals = false 83 | server, err := RunServerWithOpts(w.sOpts, w.nOpts) 84 | if err != nil && sysLog != nil { 85 | sysLog.Error(2, fmt.Sprintf("Starting server returned: %v", err)) 86 | } 87 | if err != nil { 88 | w.errCh <- err 89 | // Failed to start. 90 | return true, 1 91 | } 92 | status <- svc.Status{ 93 | State: svc.Running, 94 | Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.AcceptParamChange | acceptReopenLog, 95 | } 96 | w.srvCh <- server 97 | 98 | loop: 99 | for change := range changes { 100 | switch change.Cmd { 101 | case svc.Interrogate: 102 | status <- change.CurrentStatus 103 | case svc.Stop, svc.Shutdown: 104 | status <- svc.Status{State: svc.StopPending} 105 | server.Shutdown() 106 | break loop 107 | case reopenLogCmd: 108 | // File log re-open for rotating file logs. 109 | server.log.ReopenLogFile() 110 | case svc.ParamChange: 111 | // Ignore for now 112 | default: 113 | server.log.Debugf("Unexpected control request: %v", change.Cmd) 114 | } 115 | } 116 | 117 | status <- svc.Status{State: svc.Stopped} 118 | return false, 0 119 | } 120 | 121 | // Run starts the NATS Streaming server. This wrapper function allows Windows to add a 122 | // hook for running NATS Streaming as a service. 123 | func Run(sOpts *Options, nOpts *natsd.Options) (*StanServer, error) { 124 | if dockerized { 125 | return RunServerWithOpts(sOpts, nOpts) 126 | } 127 | run := svc.Run 128 | isInteractive, err := svc.IsAnInteractiveSession() 129 | if err != nil { 130 | return nil, err 131 | } 132 | if isInteractive { 133 | run = debug.Run 134 | } else if nOpts.Syslog || nOpts.LogFile == "" { 135 | sysLogInitLock.Lock() 136 | // We create a syslog here because we want to capture possible startup 137 | // failure message. 138 | if sysLog == nil { 139 | if sOpts.SyslogName != "" { 140 | sysLogName = sOpts.SyslogName 141 | } 142 | err := eventlog.InstallAsEventCreate(sysLogName, eventlog.Info|eventlog.Error|eventlog.Warning) 143 | if err != nil { 144 | if !strings.Contains(err.Error(), "registry key already exists") { 145 | panic(err) 146 | } 147 | } 148 | sysLog, err = eventlog.Open(sysLogName) 149 | if err != nil { 150 | panic(fmt.Sprintf("could not open event log: %v", err)) 151 | } 152 | } 153 | sysLogInitLock.Unlock() 154 | } 155 | wrapper := &winServiceWrapper{ 156 | srvCh: make(chan *StanServer, 1), 157 | errCh: make(chan error, 1), 158 | sOpts: sOpts, 159 | nOpts: nOpts, 160 | } 161 | go func() { 162 | // If no error, we exit here, otherwise, we are getting the 163 | // error down below. 164 | if err := run(serviceName, wrapper); err == nil { 165 | os.Exit(0) 166 | } 167 | }() 168 | 169 | var srv *StanServer 170 | // Wait for server instance to be created 171 | select { 172 | case err = <-wrapper.errCh: 173 | case srv = <-wrapper.srvCh: 174 | } 175 | return srv, err 176 | } 177 | 178 | // isWindowsService indicates if NATS is running as a Windows service. 179 | func isWindowsService() bool { 180 | if dockerized { 181 | return false 182 | } 183 | isInteractive, _ := svc.IsAnInteractiveSession() 184 | return !isInteractive 185 | } 186 | -------------------------------------------------------------------------------- /server/signal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2022 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //go:build !windows 15 | // +build !windows 16 | 17 | package server 18 | 19 | import ( 20 | "os" 21 | "os/signal" 22 | "syscall" 23 | 24 | natsd "github.com/nats-io/nats-server/v2/server" 25 | ) 26 | 27 | func init() { 28 | // Set the process name so signal code use this process name 29 | // instead of gnatsd. 30 | natsd.SetProcessName("nats-streaming-server") 31 | } 32 | 33 | // Signal Handling 34 | func (s *StanServer) handleSignals() { 35 | c := make(chan os.Signal, 1) 36 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGHUP) 37 | go func() { 38 | for { 39 | select { 40 | case sig := <-c: 41 | // Notify will relay only the signals that we have 42 | // registered, so we don't need a "default" in the 43 | // switch statement. 44 | switch sig { 45 | case syscall.SIGINT: 46 | s.Shutdown() 47 | os.Exit(0) 48 | case syscall.SIGTERM: 49 | s.Shutdown() 50 | os.Exit(143) 51 | case syscall.SIGUSR1: 52 | // File log re-open for rotating file logs. 53 | s.log.ReopenLogFile() 54 | case syscall.SIGHUP: 55 | s.mu.Lock() 56 | ns := s.natsServer 57 | nobr := s.natsOpts 58 | s.mu.Unlock() 59 | if ns != nil { 60 | if err := ns.Reload(); err != nil { 61 | s.log.Errorf("Reload: %v", err) 62 | } else if fileOpts, err := natsd.ProcessConfigFile(nobr.ConfigFile); err == nil { 63 | newOpts := natsd.MergeOptions(fileOpts, nobr) 64 | s.mu.Lock() 65 | s.natsOpts = newOpts.Clone() 66 | s.log.UpdateNATSOptions(s.natsOpts) 67 | s.mu.Unlock() 68 | } 69 | } else { 70 | s.log.Warnf("Reload supported only for embedded NATS Server's configuration") 71 | } 72 | } 73 | case <-s.shutdownCh: 74 | return 75 | } 76 | } 77 | }() 78 | } 79 | -------------------------------------------------------------------------------- /server/signal_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2022 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //go:build !windows 15 | // +build !windows 16 | 17 | package server 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "os" 23 | "os/exec" 24 | "strings" 25 | "sync" 26 | "syscall" 27 | "testing" 28 | "time" 29 | 30 | natsd "github.com/nats-io/nats-server/v2/server" 31 | natsdTest "github.com/nats-io/nats-server/v2/test" 32 | ) 33 | 34 | func TestSignalIgnoreUnknown(t *testing.T) { 35 | opts := GetDefaultOptions() 36 | opts.HandleSignals = true 37 | s := runServerWithOpts(t, opts, nil) 38 | defer s.Shutdown() 39 | // Send signal that we don't handle 40 | syscall.Kill(syscall.Getpid(), syscall.SIGUSR2) 41 | // Check server is still active 42 | time.Sleep(250 * time.Millisecond) 43 | if state := s.State(); state != Standalone { 44 | t.Fatalf("Expected state to be %v, got %v", Standalone.String(), state.String()) 45 | } 46 | } 47 | 48 | func TestSignalToReOpenLogFile(t *testing.T) { 49 | logFile := "test.log" 50 | f := func(iter int) { 51 | defer os.Remove(logFile) 52 | defer os.Remove(logFile + ".bak") 53 | 54 | var ns *natsd.Server 55 | if iter == 0 { 56 | ns = natsdTest.RunDefaultServer() 57 | defer ns.Shutdown() 58 | } 59 | sopts := GetDefaultOptions() 60 | sopts.HandleSignals = true 61 | nopts := &natsd.Options{ 62 | Host: "127.0.0.1", 63 | Port: -1, 64 | NoSigs: true, 65 | LogFile: logFile, 66 | } 67 | sopts.EnableLogging = true 68 | if iter == 0 { 69 | sopts.NATSServerURL = "nats://" + ns.Addr().String() 70 | } 71 | s := runServerWithOpts(t, sopts, nopts) 72 | defer s.Shutdown() 73 | 74 | // Repeat twice to ensure that signal is processed more than once 75 | for i := 0; i < 2; i++ { 76 | // Add a trace 77 | expectedStr := "This is a Notice" 78 | s.log.Noticef(expectedStr) 79 | buf, err := os.ReadFile(logFile) 80 | if err != nil { 81 | t.Fatalf("Error reading file: %v", err) 82 | } 83 | if !strings.Contains(string(buf), expectedStr) { 84 | t.Fatalf("Expected log to contain %q, got %q", expectedStr, string(buf)) 85 | } 86 | // Rename the file 87 | if err := os.Rename(logFile, logFile+".bak"); err != nil { 88 | t.Fatalf("Unable to rename file: %v", err) 89 | } 90 | // This should cause file to be reopened. 91 | syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) 92 | // Wait a bit for action to be performed 93 | waitFor(t, 2*time.Second, 15*time.Millisecond, func() error { 94 | buf, err = os.ReadFile(logFile) 95 | if err != nil { 96 | return fmt.Errorf("Error reading file: %v", err) 97 | } 98 | expectedStr = "File log re-opened" 99 | if !strings.Contains(string(buf), expectedStr) { 100 | return fmt.Errorf("Expected log to contain %q, got %q", expectedStr, string(buf)) 101 | } 102 | return nil 103 | }) 104 | // Make sure that new traces are added 105 | expectedStr = "This is a new notice" 106 | s.log.Noticef(expectedStr) 107 | buf, err = os.ReadFile(logFile) 108 | if err != nil { 109 | t.Fatalf("Error reading file: %v", err) 110 | } 111 | if !strings.Contains(string(buf), expectedStr) { 112 | t.Fatalf("Expected log to contain %q, got %q", expectedStr, string(buf)) 113 | } 114 | } 115 | } 116 | for iter := 0; iter < 2; iter++ { 117 | f(iter) 118 | } 119 | } 120 | 121 | type stderrCatcher struct { 122 | sync.Mutex 123 | b []byte 124 | } 125 | 126 | func (sc *stderrCatcher) Write(p []byte) (n int, err error) { 127 | sc.Lock() 128 | sc.b = append(sc.b, p...) 129 | sc.Unlock() 130 | return len(p), nil 131 | } 132 | 133 | func TestSignalTrapsSIGTERM(t *testing.T) { 134 | // This test requires that the server be installed. 135 | cmd := exec.Command("nats-streaming-server") 136 | sc := &stderrCatcher{} 137 | cmd.Stderr = sc 138 | cmd.Start() 139 | // Wait for it to print some startup trace 140 | waitFor(t, 2*time.Second, 15*time.Millisecond, func() error { 141 | sc.Lock() 142 | ready := bytes.Contains(sc.b, []byte(streamingReadyLog)) 143 | sc.Unlock() 144 | if ready { 145 | return nil 146 | } 147 | return fmt.Errorf("process not started yet, make sure you `go install` first!") 148 | }) 149 | syscall.Kill(cmd.Process.Pid, syscall.SIGTERM) 150 | cmd.Wait() 151 | sc.Lock() 152 | gotIt := bytes.Contains(sc.b, []byte("Shutting down")) 153 | sc.Unlock() 154 | if !gotIt { 155 | t.Fatalf("Did not get the Shutting down trace (make sure you did a `go install` prior to running the test") 156 | } 157 | } 158 | 159 | func TestSignalReload(t *testing.T) { 160 | conf := createConfFile(t, []byte(`debug: false`)) 161 | defer os.Remove(conf) 162 | // This test requires that the server be installed. 163 | cmd := exec.Command("nats-streaming-server", "-c", conf) 164 | sc := &stderrCatcher{} 165 | cmd.Stderr = sc 166 | cmd.Start() 167 | // Wait for it to print some startup trace 168 | waitFor(t, 2*time.Second, 15*time.Millisecond, func() error { 169 | sc.Lock() 170 | ready := bytes.Contains(sc.b, []byte(streamingReadyLog)) 171 | sc.Unlock() 172 | if ready { 173 | return nil 174 | } 175 | return fmt.Errorf("process not started yet, make sure you `go install` first!") 176 | }) 177 | changeCurrentConfigContentWithNewContent(t, conf, []byte(`debug: true`)) 178 | syscall.Kill(cmd.Process.Pid, syscall.SIGHUP) 179 | time.Sleep(500 * time.Millisecond) 180 | syscall.Kill(cmd.Process.Pid, syscall.SIGINT) 181 | cmd.Wait() 182 | sc.Lock() 183 | gotIt := bytes.Contains(sc.b, []byte("Reloaded: debug = true")) 184 | sc.Unlock() 185 | if !gotIt { 186 | t.Fatalf("Did not get the Reload trace (make sure you did a `go install` prior to running the test") 187 | } 188 | } 189 | 190 | func TestNATSServerConfigReloadFollowedByReopen(t *testing.T) { 191 | lfile := "nss.log" 192 | ctemp := ` 193 | logfile: "%s" 194 | debug: false 195 | trace: %s 196 | streaming { 197 | sd: false 198 | sv: false 199 | } 200 | ` 201 | conf := createConfFile(t, []byte(fmt.Sprintf(ctemp, lfile, "false"))) 202 | defer os.Remove(conf) 203 | defer os.Remove(lfile) 204 | 205 | // This test requires that the server be installed. 206 | cmd := exec.Command("nats-streaming-server", "-c", conf) 207 | cmd.Start() 208 | // Wait for it to print some startup trace 209 | waitFor(t, 2*time.Second, 50*time.Millisecond, func() error { 210 | content, err := os.ReadFile(lfile) 211 | if err != nil { 212 | return err 213 | } 214 | if bytes.Contains(content, []byte(streamingReadyLog)) { 215 | return nil 216 | } 217 | return fmt.Errorf("process not started yet, make sure you `go install` first!") 218 | }) 219 | changeCurrentConfigContentWithNewContent(t, conf, []byte(fmt.Sprintf(ctemp, lfile, "true"))) 220 | syscall.Kill(cmd.Process.Pid, syscall.SIGHUP) 221 | time.Sleep(500 * time.Millisecond) 222 | c := NewDefaultConnection(t) 223 | c.Publish("foo", []byte("hello")) 224 | c.Close() 225 | syscall.Kill(cmd.Process.Pid, syscall.SIGUSR1) 226 | time.Sleep(500 * time.Millisecond) 227 | c = NewDefaultConnection(t) 228 | c.Publish("bar", []byte("hello")) 229 | c.Close() 230 | syscall.Kill(cmd.Process.Pid, syscall.SIGINT) 231 | cmd.Wait() 232 | content, err := os.ReadFile(lfile) 233 | if err != nil { 234 | t.Fatalf("Error reading log file: %v", err) 235 | } 236 | 237 | if n := bytes.Count(content, []byte("CONNECT {")); n != 2 { 238 | t.Fatalf("Expected 2 connects, got %v\n%s\n", n, content) 239 | } 240 | idx := bytes.LastIndex(content, []byte("CONNECT {")) 241 | backward := content[idx-150 : idx] 242 | start := bytes.LastIndexByte(backward, '\n') 243 | line := backward[start:] 244 | // If we lost notion of logtime, we would have [pid] [TRC] directly.. 245 | if bytes.Contains(line, []byte("] [TRC] ")) { 246 | t.Fatalf("Logtime was lost during reload: %q", line) 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /server/signal_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package server 15 | 16 | import ( 17 | "os" 18 | "os/signal" 19 | ) 20 | 21 | // Signal Handling 22 | func (s *StanServer) handleSignals() { 23 | c := make(chan os.Signal, 1) 24 | signal.Notify(c, os.Interrupt) 25 | go func() { 26 | // We register only 1 signal (os.Interrupt) so we don't 27 | // need to check which one we get, since Notify() relays 28 | // only the ones that are registered. 29 | <-c 30 | s.Shutdown() 31 | os.Exit(0) 32 | }() 33 | } 34 | -------------------------------------------------------------------------------- /spb/protocol.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2020 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | // 14 | // Uses https://github.com/gogo/protobuf 15 | // compiled via `protoc -I=. -I=$GOPATH/src --gogofaster_out=. protocol.proto` 16 | 17 | syntax = "proto3"; 18 | package spb; 19 | 20 | import "github.com/gogo/protobuf/gogoproto/gogo.proto"; 21 | import "github.com/nats-io/stan.go/pb/protocol.proto"; 22 | 23 | option (gogoproto.marshaler_all) = true; 24 | option (gogoproto.sizer_all) = true; 25 | option (gogoproto.unmarshaler_all) = true; 26 | option (gogoproto.goproto_getters_all) = false; 27 | 28 | // SubState represents the state of a Subscription 29 | message SubState { 30 | uint64 ID = 1; // Subscription ID assigned by the SubStore interface 31 | string clientID = 2; // ClientID 32 | string qGroup = 3; // Optional queue group 33 | string inbox = 4; // Inbox subject to deliver messages on 34 | string ackInbox = 5; // Inbox for acks 35 | int32 maxInFlight = 6; // Maximum inflight messages without an ack allowed 36 | int32 ackWaitInSecs = 7; // Timeout for receiving an ack from the client 37 | string durableName = 8; // Optional durable name which survives client restarts 38 | uint64 lastSent = 9; // Start position 39 | bool isDurable =10; // Indicate durability for this subscriber 40 | bool isClosed =11; // Indicate that the durable subscriber is closed 41 | } 42 | 43 | // SubStateDelete marks a Subscription as deleted 44 | message SubStateDelete { 45 | uint64 ID = 1; // Subscription ID being deleted 46 | } 47 | 48 | // SubStateUpdate represents a subscription update (either Msg or Ack) 49 | message SubStateUpdate { 50 | uint64 ID = 1; // Subscription ID 51 | uint64 seqno = 2; // Sequence of the message (pending or ack'ed) 52 | } 53 | 54 | // ServerInfo contains basic information regarding the Server 55 | message ServerInfo { 56 | string ClusterID = 1; // Cluster ID 57 | string Discovery = 2; // Subject server receives connect requests on. 58 | string Publish = 3; // Subject prefix server receives published messages on. 59 | string Subscribe = 4; // Subject server receives subscription requests on. 60 | string Unsubscribe = 5; // Subject server receives unsubscribe requests on. 61 | string Close = 6; // Subject server receives close requests on. 62 | string SubClose = 7; // Subject server receives subscription close requests on. 63 | string AcksSubs = 8; // Subject prefix server receives subscription acks when using pool of ack subscribers. 64 | string NodeID = 9; // Clustering node ID. 65 | } 66 | 67 | // ClientInfo contains information related to a Client 68 | message ClientInfo { 69 | string ID = 1; // Client ID 70 | string HbInbox = 2; // The inbox heartbeats are sent to 71 | bytes ConnID = 3; // Connection ID (no connection should ever have the same) 72 | int32 Protocol = 4; // Protocol the client is at 73 | int32 PingInterval = 5; // Interval at which the client is sending PINGs (expressed in seconds) 74 | int32 PingMaxOut = 6; // Number of PINGs without response before the connection can be considered lost 75 | } 76 | 77 | message ClientDelete { 78 | string ID = 1; // ID of the client being unregistered 79 | } 80 | 81 | message CtrlMsg { 82 | enum Type { 83 | SubUnsubscribe = 0; // DEPRECATED. 84 | SubClose = 1; // DEPRECATED. 85 | ConnClose = 2; // DEPRECATED. 86 | FTHeartbeat = 3; // FT heartbeats. 87 | Partitioning = 4; // When partitioning is enabled, server sends this to other servers with same cluster ID. 88 | } 89 | Type MsgType = 1; // Type of the control message. 90 | string ServerID = 2; // Allows a server to detect if it is the intended receipient. 91 | bytes Data = 3; // Optional bytes that carries context information. 92 | 93 | string RefID = 4; // DEPRECATED. 94 | } 95 | 96 | // RaftJoinRequest is a request to join a Raft group. 97 | message RaftJoinRequest { 98 | string NodeID = 1; // ID of the joining node. 99 | string NodeAddr = 2; // Address of the joining node. 100 | } 101 | 102 | // RaftJoinResponse is a response to a RaftJoinRequest. 103 | message RaftJoinResponse { 104 | string Error = 1; // Error string, omitted if no error. 105 | } 106 | 107 | // RaftOperation is a Raft log message. 108 | message RaftOperation { 109 | enum Type { 110 | Publish = 0; // Message publish. 111 | Subscribe = 1; // Create client subscription. 112 | RemoveSubscription = 2; // Remove client subscription. 113 | CloseSubscription = 3; // Close durable client subscription. 114 | SendAndAck = 4; // Messages send or ack'ed. 115 | Connect = 6; // Client connection. 116 | Disconnect = 7; // Client disconnect. 117 | DeleteChannel = 8; // Delete the channel. 118 | } 119 | Type OpType = 1; // Log message type. 120 | Batch PublishBatch = 2; // Publish operation data. 121 | AddSubscription Sub = 3; // Subscribe operation data. 122 | pb.UnsubscribeRequest Unsub = 4; // Close/Remove Subscription operation data. 123 | SubSentAndAck SubSentAck = 5; // Send and/or Ack operation data. 124 | AddClient ClientConnect = 7; // Connect operation data. 125 | pb.CloseRequest ClientDisconnect = 8; // Disconnect operation data. 126 | string Channel = 9; // Channel name. 127 | uint64 ChannelID =10; // Channel ID. 128 | } 129 | 130 | // Batch is a batch of messages for replication. 131 | message Batch { 132 | repeated pb.MsgProto Messages = 1; // Serialized MsgProtos to replicate. 133 | } 134 | 135 | // AddSubscription is used to replicate a new client subscription. 136 | message AddSubscription { 137 | pb.SubscriptionRequest Request = 1; // Subscription request to replicate. 138 | string AckInbox = 2; // Ack inbox for the subscription. 139 | uint64 ID = 3; // Subscription ID. 140 | } 141 | 142 | // SubSentAndAck is used to replicate a sent and/or ack messages. 143 | message SubSentAndAck { 144 | string Channel = 1; // Subscription channel. 145 | string AckInbox = 2; // Subscription ack inbox. 146 | repeated uint64 Sent = 3; // Message sequences that were sent. 147 | repeated uint64 Ack = 4; // Message sequences that were ack'ed. 148 | } 149 | 150 | // AddClient is used to replicate a new client connection. 151 | message AddClient { 152 | pb.ConnectRequest Request = 1; // Connection request to replicate 153 | bool Refresh = 2; // Whether or not this is an existing client being refreshed. 154 | } 155 | 156 | // RaftSnapshot is a snapshot of the state of the server. 157 | message RaftSnapshot { 158 | repeated ClientInfo Clients = 1; 159 | repeated ChannelSnapshot Channels = 2; 160 | bytes Padding = 3; 161 | } 162 | 163 | // ChannelSnapshot is a snapshot of a channel 164 | message ChannelSnapshot { 165 | string Channel = 1; 166 | uint64 First = 2; 167 | uint64 Last = 3; 168 | repeated SubscriptionSnapshot Subscriptions = 4; 169 | uint64 NextSubID = 5; 170 | uint64 ChannelID = 6; 171 | } 172 | 173 | // SubscriptionSnaphot is the snapshot of a subscription 174 | message SubscriptionSnapshot { 175 | SubState State = 1; // Subscription data. 176 | repeated uint64 AcksPending = 2; // Sequences of unacknowledged messages. 177 | } 178 | -------------------------------------------------------------------------------- /stores/memstore.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package stores 15 | 16 | import ( 17 | "sort" 18 | "sync" 19 | "time" 20 | 21 | "github.com/nats-io/nats-streaming-server/logger" 22 | "github.com/nats-io/nats-streaming-server/util" 23 | "github.com/nats-io/stan.go/pb" 24 | ) 25 | 26 | // MemoryStore is a factory for message and subscription stores. 27 | type MemoryStore struct { 28 | genericStore 29 | } 30 | 31 | // MemorySubStore is a subscription store in memory 32 | type MemorySubStore struct { 33 | genericSubStore 34 | } 35 | 36 | // MemoryMsgStore is a per channel message store in memory 37 | type MemoryMsgStore struct { 38 | genericMsgStore 39 | msgs map[uint64]*pb.MsgProto 40 | ageTimer *time.Timer 41 | wg sync.WaitGroup 42 | } 43 | 44 | //////////////////////////////////////////////////////////////////////////// 45 | // MemoryStore methods 46 | //////////////////////////////////////////////////////////////////////////// 47 | 48 | // NewMemoryStore returns a factory for stores held in memory. 49 | // If not limits are provided, the store will be created with 50 | // DefaultStoreLimits. 51 | func NewMemoryStore(log logger.Logger, limits *StoreLimits) (*MemoryStore, error) { 52 | ms := &MemoryStore{} 53 | if err := ms.init(TypeMemory, log, limits); err != nil { 54 | return nil, err 55 | } 56 | return ms, nil 57 | } 58 | 59 | // CreateChannel implements the Store interface 60 | func (ms *MemoryStore) CreateChannel(channel string) (*Channel, error) { 61 | ms.Lock() 62 | defer ms.Unlock() 63 | 64 | // Verify that it does not already exist or that we did not hit the limits 65 | if err := ms.canAddChannel(channel); err != nil { 66 | return nil, err 67 | } 68 | 69 | channelLimits := ms.genericStore.getChannelLimits(channel) 70 | 71 | msgStore := &MemoryMsgStore{msgs: make(map[uint64]*pb.MsgProto, 64)} 72 | msgStore.init(channel, ms.log, &channelLimits.MsgStoreLimits) 73 | 74 | subStore := &MemorySubStore{} 75 | subStore.init(ms.log, &channelLimits.SubStoreLimits) 76 | 77 | c := &Channel{ 78 | Subs: subStore, 79 | Msgs: msgStore, 80 | } 81 | ms.channels[channel] = c 82 | 83 | return c, nil 84 | } 85 | 86 | //////////////////////////////////////////////////////////////////////////// 87 | // MemoryMsgStore methods 88 | //////////////////////////////////////////////////////////////////////////// 89 | 90 | // Store a given message. 91 | func (ms *MemoryMsgStore) Store(m *pb.MsgProto) (uint64, error) { 92 | ms.Lock() 93 | defer ms.Unlock() 94 | 95 | if m.Sequence <= ms.last { 96 | // We've already seen this message. 97 | return m.Sequence, nil 98 | } 99 | 100 | if ms.first == 0 { 101 | ms.first = m.Sequence 102 | } 103 | ms.last = m.Sequence 104 | ms.msgs[ms.last] = m 105 | ms.totalCount++ 106 | ms.totalBytes += uint64(m.Size()) 107 | // If there is an age limit and no timer yet created, do so now 108 | if ms.limits.MaxAge > time.Duration(0) && ms.ageTimer == nil { 109 | ms.wg.Add(1) 110 | ms.ageTimer = time.AfterFunc(ms.msgExpireIn(m.Timestamp), ms.expireMsgs) 111 | } 112 | 113 | // Check if we need to remove any (but leave at least the last added) 114 | maxMsgs := ms.limits.MaxMsgs 115 | maxBytes := ms.limits.MaxBytes 116 | if maxMsgs > 0 || maxBytes > 0 { 117 | for ms.totalCount > 1 && 118 | ((maxMsgs > 0 && ms.totalCount > maxMsgs) || 119 | (maxBytes > 0 && (ms.totalBytes > uint64(maxBytes)))) { 120 | ms.removeFirstMsg() 121 | if !ms.hitLimit { 122 | ms.hitLimit = true 123 | ms.log.Warnf(droppingMsgsFmt, ms.subject, ms.totalCount, ms.limits.MaxMsgs, 124 | util.FriendlyBytes(int64(ms.totalBytes)), util.FriendlyBytes(ms.limits.MaxBytes)) 125 | } 126 | } 127 | } 128 | 129 | return ms.last, nil 130 | } 131 | 132 | // Lookup returns the stored message with given sequence number. 133 | func (ms *MemoryMsgStore) Lookup(seq uint64) (*pb.MsgProto, error) { 134 | ms.RLock() 135 | m := ms.msgs[seq] 136 | ms.RUnlock() 137 | return m, nil 138 | } 139 | 140 | // FirstMsg returns the first message stored. 141 | func (ms *MemoryMsgStore) FirstMsg() (*pb.MsgProto, error) { 142 | ms.RLock() 143 | m := ms.msgs[ms.first] 144 | ms.RUnlock() 145 | return m, nil 146 | } 147 | 148 | // LastMsg returns the last message stored. 149 | func (ms *MemoryMsgStore) LastMsg() (*pb.MsgProto, error) { 150 | ms.RLock() 151 | m := ms.msgs[ms.last] 152 | ms.RUnlock() 153 | return m, nil 154 | } 155 | 156 | // GetSequenceFromTimestamp returns the sequence of the first message whose 157 | // timestamp is greater or equal to given timestamp. 158 | func (ms *MemoryMsgStore) GetSequenceFromTimestamp(timestamp int64) (uint64, error) { 159 | ms.RLock() 160 | defer ms.RUnlock() 161 | 162 | // No message ever stored 163 | if ms.first == 0 { 164 | return 0, nil 165 | } 166 | // All messages have expired 167 | if ms.first > ms.last { 168 | return ms.last + 1, nil 169 | } 170 | if timestamp <= ms.msgs[ms.first].Timestamp { 171 | return ms.first, nil 172 | } 173 | if timestamp == ms.msgs[ms.last].Timestamp { 174 | return ms.last, nil 175 | } 176 | if timestamp > ms.msgs[ms.last].Timestamp { 177 | return ms.last + 1, nil 178 | } 179 | 180 | index := sort.Search(len(ms.msgs), func(i int) bool { 181 | return ms.msgs[uint64(i)+ms.first].Timestamp >= timestamp 182 | }) 183 | 184 | return uint64(index) + ms.first, nil 185 | } 186 | 187 | // expireMsgs ensures that messages don't stay in the log longer than the 188 | // limit's MaxAge. 189 | func (ms *MemoryMsgStore) expireMsgs() { 190 | ms.Lock() 191 | defer ms.Unlock() 192 | if ms.closed { 193 | ms.wg.Done() 194 | return 195 | } 196 | 197 | now := time.Now().UnixNano() 198 | maxAge := int64(ms.limits.MaxAge) 199 | for { 200 | m, ok := ms.msgs[ms.first] 201 | if !ok { 202 | if ms.first < ms.last { 203 | ms.first++ 204 | continue 205 | } 206 | ms.ageTimer = nil 207 | ms.wg.Done() 208 | return 209 | } 210 | elapsed := now - m.Timestamp 211 | if elapsed >= maxAge { 212 | ms.removeFirstMsg() 213 | } else { 214 | if elapsed < 0 { 215 | ms.ageTimer.Reset(time.Duration(m.Timestamp - now + maxAge)) 216 | } else { 217 | ms.ageTimer.Reset(time.Duration(maxAge - elapsed)) 218 | } 219 | return 220 | } 221 | } 222 | } 223 | 224 | // removeFirstMsg removes the first message and updates totals. 225 | func (ms *MemoryMsgStore) removeFirstMsg() { 226 | firstMsg := ms.msgs[ms.first] 227 | ms.totalBytes -= uint64(firstMsg.Size()) 228 | ms.totalCount-- 229 | delete(ms.msgs, ms.first) 230 | ms.first++ 231 | } 232 | 233 | // Empty implements the MsgStore interface 234 | func (ms *MemoryMsgStore) Empty() error { 235 | ms.Lock() 236 | if ms.ageTimer != nil { 237 | if ms.ageTimer.Stop() { 238 | ms.wg.Done() 239 | } 240 | ms.ageTimer = nil 241 | } 242 | ms.empty() 243 | ms.msgs = make(map[uint64]*pb.MsgProto) 244 | ms.Unlock() 245 | return nil 246 | } 247 | 248 | // Close implements the MsgStore interface 249 | func (ms *MemoryMsgStore) Close() error { 250 | ms.Lock() 251 | if ms.closed { 252 | ms.Unlock() 253 | return nil 254 | } 255 | ms.closed = true 256 | if ms.ageTimer != nil { 257 | if ms.ageTimer.Stop() { 258 | ms.wg.Done() 259 | } 260 | } 261 | ms.Unlock() 262 | 263 | ms.wg.Wait() 264 | return nil 265 | } 266 | 267 | //////////////////////////////////////////////////////////////////////////// 268 | // MemorySubStore methods 269 | //////////////////////////////////////////////////////////////////////////// 270 | 271 | // AddSeqPending adds the given message seqno to the given subscription. 272 | func (*MemorySubStore) AddSeqPending(subid, seqno uint64) error { 273 | // Overrides in case genericSubStore does something. For the memory 274 | // based store, we want to minimize the cost of this to a minimum. 275 | return nil 276 | } 277 | 278 | // AckSeqPending records that the given message seqno has been acknowledged 279 | // by the given subscription. 280 | func (*MemorySubStore) AckSeqPending(subid, seqno uint64) error { 281 | // Overrides in case genericSubStore does something. For the memory 282 | // based store, we want to minimize the cost of this to a minimum. 283 | return nil 284 | } 285 | -------------------------------------------------------------------------------- /stores/memstore_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2018 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package stores 15 | 16 | import ( 17 | "reflect" 18 | "testing" 19 | "time" 20 | ) 21 | 22 | func createDefaultMemStore(t tLogger) *MemoryStore { 23 | limits := testDefaultStoreLimits 24 | ms, err := NewMemoryStore(testLogger, &limits) 25 | if err != nil { 26 | stackFatalf(t, "Unexpected error: %v", err) 27 | } 28 | return ms 29 | } 30 | 31 | func TestMSUseDefaultLimits(t *testing.T) { 32 | ms, err := NewMemoryStore(testLogger, nil) 33 | if err != nil { 34 | t.Fatalf("Unexpected error: %v", err) 35 | } 36 | defer ms.Close() 37 | if !reflect.DeepEqual(*ms.limits, DefaultStoreLimits) { 38 | t.Fatalf("Default limits are not used: %v\n", *ms.limits) 39 | } 40 | } 41 | 42 | func TestMSGetExclusiveLock(t *testing.T) { 43 | ms := createDefaultMemStore(t) 44 | defer ms.Close() 45 | // GetExclusiveLock is not supported 46 | locked, err := ms.GetExclusiveLock() 47 | if err == nil { 48 | t.Fatal("Should have failed, it did not") 49 | } 50 | if locked { 51 | t.Fatal("Should not be locked") 52 | } 53 | } 54 | 55 | func TestMSNegativeLimitsOnCreate(t *testing.T) { 56 | limits := DefaultStoreLimits 57 | limits.MaxMsgs = -1000 58 | if ms, err := NewMemoryStore(testLogger, &limits); ms != nil || err == nil { 59 | if ms != nil { 60 | ms.Close() 61 | } 62 | t.Fatal("Should have failed to create store with a negative limit") 63 | } 64 | } 65 | 66 | func TestMSMsgStoreEmpty(t *testing.T) { 67 | s := createDefaultMemStore(t) 68 | defer s.Close() 69 | 70 | limits := StoreLimits{} 71 | limits.MaxAge = 250 * time.Millisecond 72 | if err := s.SetLimits(&limits); err != nil { 73 | t.Fatalf("Error setting limits: %v", err) 74 | } 75 | 76 | cs := storeCreateChannel(t, s, "foo") 77 | 78 | // Send some messages 79 | for i := 0; i < 3; i++ { 80 | storeMsg(t, cs, "foo", uint64(i+1), []byte("hello")) 81 | } 82 | // Then empty the message store 83 | if err := cs.Msgs.Empty(); err != nil { 84 | t.Fatalf("Error on Empty(): %v", err) 85 | } 86 | 87 | ms := cs.Msgs.(*MemoryMsgStore) 88 | ms.RLock() 89 | if ms.ageTimer != nil { 90 | ms.RUnlock() 91 | t.Fatal("AgeTimer not nil") 92 | } 93 | if ms.first != 0 || ms.last != 0 { 94 | ms.RUnlock() 95 | t.Fatalf("First and/or Last not reset") 96 | } 97 | ms.RUnlock() 98 | } 99 | -------------------------------------------------------------------------------- /stores/pqdeadlines/pqdeadlines.go: -------------------------------------------------------------------------------- 1 | package pqdeadlines 2 | 3 | import ( 4 | "database/sql" 5 | "database/sql/driver" 6 | "fmt" 7 | "net" 8 | "strings" 9 | "time" 10 | 11 | "github.com/lib/pq" 12 | ) 13 | 14 | func init() { 15 | sql.Register("pq-deadlines", deadlineDriver{}) 16 | } 17 | 18 | type deadlineDriver struct{} 19 | 20 | type deadlineDialer struct { 21 | readTimeout time.Duration 22 | writeTimeout time.Duration 23 | } 24 | 25 | type deadlineConn struct { 26 | net.Conn 27 | readTimeout time.Duration 28 | writeTimeout time.Duration 29 | } 30 | 31 | func (c *deadlineConn) Read(b []byte) (n int, err error) { 32 | if c.readTimeout != 0 { 33 | c.Conn.SetReadDeadline(time.Now().Add(c.readTimeout)) 34 | } 35 | n, err = c.Conn.Read(b) 36 | if c.readTimeout != 0 { 37 | c.Conn.SetReadDeadline(time.Time{}) 38 | } 39 | return n, err 40 | } 41 | 42 | func (c *deadlineConn) Write(b []byte) (n int, err error) { 43 | if c.writeTimeout != 0 { 44 | c.Conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 45 | } 46 | n, err = c.Conn.Write(b) 47 | if c.writeTimeout != 0 { 48 | c.Conn.SetWriteDeadline(time.Time{}) 49 | } 50 | return n, err 51 | } 52 | 53 | func (d deadlineDialer) Dial(network string, address string) (net.Conn, error) { 54 | return d.DialTimeout(network, address, 0) 55 | } 56 | 57 | func (d deadlineDialer) DialTimeout(network string, address string, timeout time.Duration) (net.Conn, error) { 58 | c, err := net.DialTimeout(network, address, timeout) 59 | if err != nil { 60 | return c, err 61 | } 62 | if d.readTimeout == 0 && d.writeTimeout == 0 { 63 | return c, nil 64 | } 65 | return &deadlineConn{Conn: c, readTimeout: d.readTimeout, writeTimeout: d.writeTimeout}, nil 66 | } 67 | 68 | func (t deadlineDriver) Open(connection string) (driver.Conn, error) { 69 | var ( 70 | connKeyVals []string 71 | readTimeout time.Duration 72 | writeTimeout time.Duration 73 | err error 74 | ) 75 | 76 | if strings.HasPrefix(connection, "postgres://") || strings.HasPrefix(connection, "postgresql://") { 77 | connection, err = pq.ParseURL(connection) 78 | if err != nil { 79 | return nil, err 80 | } 81 | } 82 | 83 | for _, keyVal := range strings.Fields(connection) { 84 | s := strings.Split(keyVal, "=") 85 | key := strings.ToLower(s[0]) 86 | switch key { 87 | case "readtimeout": 88 | readTimeout, err = time.ParseDuration(s[1]) 89 | if err != nil { 90 | return nil, fmt.Errorf("unable to parse readTimeout: %v", err) 91 | } 92 | case "writetimeout": 93 | writeTimeout, err = time.ParseDuration(s[1]) 94 | if err != nil { 95 | return nil, fmt.Errorf("unable to parse writeTimeout: %v", err) 96 | } 97 | default: 98 | connKeyVals = append(connKeyVals, keyVal) 99 | } 100 | } 101 | 102 | connStr := strings.Join(connKeyVals, " ") 103 | td := deadlineDialer{ 104 | readTimeout: readTimeout, 105 | writeTimeout: writeTimeout, 106 | } 107 | return pq.DialOpen(td, connStr) 108 | } 109 | -------------------------------------------------------------------------------- /stores/raftstore.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package stores 15 | 16 | import ( 17 | "sync" 18 | 19 | "github.com/nats-io/nats-streaming-server/logger" 20 | "github.com/nats-io/nats-streaming-server/spb" 21 | ) 22 | 23 | // RaftStore is an hybrid store for server running in clustering mode. 24 | // This store persists/recovers ServerInfo and messages, but is a no-op 25 | // for clients and subscriptions since we rely on raft log for that. 26 | // It still creates/deletes subscriptions so that we on recovery we 27 | // can ensure that we don't reuse any subscription ID. 28 | type RaftStore struct { 29 | sync.Mutex 30 | Store 31 | log logger.Logger 32 | } 33 | 34 | // RaftSubStore implements the SubStore interface 35 | type RaftSubStore struct { 36 | genericSubStore 37 | } 38 | 39 | // NewRaftStore returns an instarce of a RaftStore 40 | func NewRaftStore(log logger.Logger, s Store, limits *StoreLimits) *RaftStore { 41 | return &RaftStore{Store: s, log: log} 42 | } 43 | 44 | //////////////////////////////////////////////////////////////////////////// 45 | // RaftStore methods 46 | //////////////////////////////////////////////////////////////////////////// 47 | 48 | // CreateChannel implements the Store interface 49 | func (s *RaftStore) CreateChannel(channel string) (*Channel, error) { 50 | s.Lock() 51 | defer s.Unlock() 52 | c, err := s.Store.CreateChannel(channel) 53 | if err != nil { 54 | return nil, err 55 | } 56 | c.Subs = s.replaceSubStore(channel, c.Subs, 0) 57 | return c, nil 58 | } 59 | 60 | func (s *RaftStore) replaceSubStore(channel string, realSubStore SubStore, maxSubID uint64) *RaftSubStore { 61 | // Close underlying sub store. 62 | realSubStore.Close() 63 | // We need the subs limits for this channel 64 | cl := s.Store.GetChannelLimits(channel) 65 | // Create and initialize our sub store. 66 | rss := &RaftSubStore{} 67 | rss.init(s.log, &cl.SubStoreLimits) 68 | rss.maxSubID = maxSubID 69 | return rss 70 | } 71 | 72 | // Name implements the Store interface 73 | func (s *RaftStore) Name() string { 74 | return TypeRaft + "_" + s.Store.Name() 75 | } 76 | 77 | // Recover implements the Store interface 78 | func (s *RaftStore) Recover() (*RecoveredState, error) { 79 | s.Lock() 80 | defer s.Unlock() 81 | state, err := s.Store.Recover() 82 | if err != nil { 83 | return nil, err 84 | } 85 | if state != nil { 86 | for channel, rc := range state.Channels { 87 | // Note that this is when recovering the underlying sub store 88 | // that would be the case for a RaftSubStore prior to 0.14.1 89 | var maxSubID uint64 90 | for _, rs := range rc.Subscriptions { 91 | if rs.Sub.ID > maxSubID { 92 | maxSubID = rs.Sub.ID 93 | } 94 | } 95 | rc.Channel.Subs = s.replaceSubStore(channel, rc.Channel.Subs, maxSubID) 96 | rc.Subscriptions = nil 97 | } 98 | state.Clients = nil 99 | } 100 | return state, nil 101 | } 102 | 103 | // AddClient implements the Store interface 104 | func (s *RaftStore) AddClient(info *spb.ClientInfo) (*Client, error) { 105 | // No need for storage 106 | return &Client{*info}, nil 107 | } 108 | 109 | // DeleteClient implements the Store interface 110 | func (s *RaftStore) DeleteClient(clientID string) error { 111 | // Make this a no-op 112 | return nil 113 | } 114 | 115 | //////////////////////////////////////////////////////////////////////////// 116 | // RaftSubStore methods 117 | //////////////////////////////////////////////////////////////////////////// 118 | 119 | // CreateSub implements the SubStore interface 120 | func (ss *RaftSubStore) CreateSub(sub *spb.SubState) error { 121 | gss := &ss.genericSubStore 122 | 123 | gss.Lock() 124 | defer gss.Unlock() 125 | 126 | // This store does not persist subscriptions, since it is done 127 | // in the actual RAFT log. This is just a wrapper to the streaming 128 | // sub store. We still need to apply limits. 129 | 130 | // If sub.ID is provided, check if already present, in which case 131 | // don't check limit. 132 | if sub.ID > 0 { 133 | if _, ok := gss.subs[sub.ID]; ok { 134 | return nil 135 | } 136 | } 137 | // Check limits 138 | if gss.limits.MaxSubscriptions > 0 && len(gss.subs) >= gss.limits.MaxSubscriptions { 139 | return ErrTooManySubs 140 | } 141 | 142 | // With new server, the sub.ID is set before this call is invoked, 143 | // and if that is the case, this is what we use. But let's support 144 | // not having one (in case we recover an existing store, or run a 145 | // mix of servers with different versions where the leader would 146 | // not be at a version that sets the sub.ID). 147 | if sub.ID == 0 { 148 | gss.maxSubID++ 149 | sub.ID = gss.maxSubID 150 | } else if sub.ID > gss.maxSubID { 151 | gss.maxSubID = sub.ID 152 | } 153 | gss.subs[sub.ID] = emptySub 154 | 155 | return nil 156 | } 157 | 158 | // UpdateSub implements the SubStore interface 159 | func (ss *RaftSubStore) UpdateSub(*spb.SubState) error { 160 | // Make this a no-op 161 | return nil 162 | } 163 | 164 | // AddSeqPending adds the given message 'seqno' to the subscription 'subid'. 165 | func (ss *RaftSubStore) AddSeqPending(subid, seqno uint64) error { 166 | // Make this a no-op 167 | return nil 168 | } 169 | 170 | // AckSeqPending records that the given message 'seqno' has been acknowledged 171 | // by the subscription 'subid'. 172 | func (ss *RaftSubStore) AckSeqPending(subid, seqno uint64) error { 173 | // Make this a no-op 174 | return nil 175 | } 176 | -------------------------------------------------------------------------------- /stores/raftstore_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package stores 15 | 16 | import ( 17 | "fmt" 18 | "os" 19 | "reflect" 20 | "testing" 21 | "time" 22 | 23 | "github.com/nats-io/nats-streaming-server/spb" 24 | ) 25 | 26 | var testRSDefaultDatastore string 27 | 28 | func init() { 29 | tmpDir, err := os.MkdirTemp(".", "raft_data_stores_") 30 | if err != nil { 31 | panic("Could not create tmp dir") 32 | } 33 | if err := os.Remove(tmpDir); err != nil { 34 | panic(fmt.Errorf("Error removing temp directory: %v", err)) 35 | } 36 | testRSDefaultDatastore = tmpDir 37 | } 38 | 39 | func cleanupRaftDatastore(t tLogger) { 40 | if err := os.RemoveAll(testRSDefaultDatastore); err != nil { 41 | stackFatalf(t, "Error cleaning up datastore: %v", err) 42 | } 43 | } 44 | 45 | func createDefaultRaftStore(t tLogger) *RaftStore { 46 | limits := testDefaultStoreLimits 47 | fs, err := NewFileStore(testLogger, testRSDefaultDatastore, &limits) 48 | if err != nil { 49 | stackFatalf(t, "Error creating raft store: %v", err) 50 | } 51 | rs := NewRaftStore(testLogger, fs, &limits) 52 | state, err := rs.Recover() 53 | if err != nil { 54 | rs.Close() 55 | stackFatalf(t, "Error recovering file store: %v", err) 56 | } 57 | if state == nil { 58 | info := testDefaultServerInfo 59 | if err := rs.Init(&info); err != nil { 60 | stackFatalf(t, "Unexpected error durint Init: %v", err) 61 | } 62 | } 63 | return rs 64 | } 65 | 66 | func TestRSRecover(t *testing.T) { 67 | cleanupRaftDatastore(t) 68 | defer cleanupRaftDatastore(t) 69 | 70 | limits := testDefaultStoreLimits 71 | fs, err := NewFileStore(testLogger, testRSDefaultDatastore, &limits) 72 | if err != nil { 73 | t.Fatalf("Error creating store: %v", err) 74 | } 75 | s := NewRaftStore(testLogger, fs, &limits) 76 | defer s.Close() 77 | 78 | info := testDefaultServerInfo 79 | info.ClusterID = "testRaftStore" 80 | if err := s.Init(&info); err != nil { 81 | t.Fatalf("Error on init: %v", err) 82 | } 83 | 84 | // Add some clients activity 85 | storeAddClient(t, s, "me", "mehbinbox") 86 | storeAddClient(t, s, "me2", "me2hbinbox") 87 | storeAddClient(t, s, "me3", "me3hbinbox") 88 | storeDeleteClient(t, s, "me") 89 | 90 | // Add some messages 91 | cs := storeCreateChannel(t, s, "foo") 92 | for i := 0; i < 10; i++ { 93 | storeMsg(t, cs, "foo", uint64(i+1), []byte("msg")) 94 | } 95 | 96 | // Add some subscriptions activity 97 | sub1 := storeSub(t, cs, "foo") 98 | sub2 := storeSub(t, cs, "foo") 99 | storeSubPending(t, cs, "foo", sub1, 1, 2, 3) 100 | storeSubAck(t, cs, "foo", sub1, 1, 3) 101 | storeSubPending(t, cs, "foo", sub2, 1, 2, 3, 4, 5) 102 | storeSubAck(t, cs, "foo", sub2, 1, 2, 3, 4) 103 | storeSubDelete(t, cs, "foo", sub2) 104 | 105 | // Close store and re-open it 106 | s.Close() 107 | 108 | fs, err = NewFileStore(testLogger, testRSDefaultDatastore, &limits) 109 | if err != nil { 110 | stackFatalf(t, "Error creating raft store: %v", err) 111 | } 112 | s = NewRaftStore(testLogger, fs, &limits) 113 | defer s.Close() 114 | state, err := s.Recover() 115 | if err != nil { 116 | t.Fatalf("Error on recover: %v", err) 117 | } 118 | if state == nil { 119 | t.Fatal("Expected a state, did not get one") 120 | } 121 | if !reflect.DeepEqual(*state.Info, info) { 122 | t.Fatalf("Expected ServerInfo to be %v, got %v", *state.Info, info) 123 | } 124 | // Expect single channel "foo" 125 | if len(state.Channels) != 1 { 126 | t.Fatalf("Expected only 1 channel, got %v", len(state.Channels)) 127 | } 128 | cs = getRecoveredChannel(t, state, "foo") 129 | // Expect no client 130 | if len(state.Clients) != 0 { 131 | t.Fatalf("Expected no client, got %v", len(state.Clients)) 132 | } 133 | // Expect no subscription 134 | for _, rc := range state.Channels { 135 | if len(rc.Subscriptions) != 0 { 136 | t.Fatalf("Should have no subscription, got %v", len(rc.Subscriptions)) 137 | } 138 | } 139 | // Expect 10 messages 140 | count, _ := msgStoreState(t, cs.Msgs) 141 | if count != 10 { 142 | t.Fatalf("Expected 10 messages, got %v", count) 143 | } 144 | } 145 | 146 | func TestRSRecoverOldStore(t *testing.T) { 147 | cleanupRaftDatastore(t) 148 | defer cleanupRaftDatastore(t) 149 | 150 | limits := testDefaultStoreLimits 151 | fs, err := NewFileStore(testLogger, testRSDefaultDatastore, &limits) 152 | if err != nil { 153 | t.Fatalf("Error creating store: %v", err) 154 | } 155 | 156 | info := testDefaultServerInfo 157 | info.ClusterID = "testRaftStore" 158 | if err := fs.Init(&info); err != nil { 159 | t.Fatalf("Error on init: %v", err) 160 | } 161 | 162 | // Add some messages 163 | cs := storeCreateChannel(t, fs, "foo") 164 | for i := 0; i < 10; i++ { 165 | storeMsg(t, cs, "foo", uint64(i+1), []byte("msg")) 166 | } 167 | 168 | // Add some subscriptions activity 169 | sub1 := storeSub(t, cs, "foo") 170 | sub2 := storeSub(t, cs, "foo") 171 | storeSubPending(t, cs, "foo", sub1, 1, 2, 3) 172 | storeSubAck(t, cs, "foo", sub1, 1, 3) 173 | storeSubPending(t, cs, "foo", sub2, 1, 2, 3, 4, 5) 174 | storeSubAck(t, cs, "foo", sub2, 1, 2, 3, 4) 175 | 176 | // Close store and re-open it 177 | fs.Close() 178 | 179 | fs, err = NewFileStore(testLogger, testRSDefaultDatastore, &limits) 180 | if err != nil { 181 | stackFatalf(t, "Error creating raft store: %v", err) 182 | } 183 | s := NewRaftStore(testLogger, fs, &limits) 184 | defer s.Close() 185 | state, err := s.Recover() 186 | if err != nil { 187 | t.Fatalf("Error on recover: %v", err) 188 | } 189 | if state == nil { 190 | t.Fatal("Expected a state, did not get one") 191 | } 192 | if !reflect.DeepEqual(*state.Info, info) { 193 | t.Fatalf("Expected ServerInfo to be %v, got %v", *state.Info, info) 194 | } 195 | // Expect single channel "foo" 196 | if len(state.Channels) != 1 { 197 | t.Fatalf("Expected only 1 channel, got %v", len(state.Channels)) 198 | } 199 | cs = getRecoveredChannel(t, state, "foo") 200 | // Expect no client 201 | if len(state.Clients) != 0 { 202 | t.Fatalf("Expected no client, got %v", len(state.Clients)) 203 | } 204 | // Expect no subscription 205 | for _, rc := range state.Channels { 206 | if len(rc.Subscriptions) != 0 { 207 | t.Fatalf("Should have no subscription, got %v", len(rc.Subscriptions)) 208 | } 209 | } 210 | // Expect 10 messages 211 | count, _ := msgStoreState(t, cs.Msgs) 212 | if count != 10 { 213 | t.Fatalf("Expected 10 messages, got %v", count) 214 | } 215 | // Adding a new subscription should give us ID 3 216 | sub3 := storeSub(t, cs, "foo") 217 | if sub3 != 3 { 218 | t.Fatalf("Expected sub3 to have ID 3, got %v", sub3) 219 | } 220 | } 221 | 222 | func TestRSUseSubID(t *testing.T) { 223 | cleanupRaftDatastore(t) 224 | defer cleanupRaftDatastore(t) 225 | 226 | limits := testDefaultStoreLimits 227 | limits.MaxSubscriptions = 2 228 | ms, err := NewMemoryStore(testLogger, &limits) 229 | if err != nil { 230 | t.Fatalf("Error creating store: %v", err) 231 | } 232 | s := NewRaftStore(testLogger, ms, &limits) 233 | defer s.Close() 234 | 235 | cs := storeCreateChannel(t, s, "foo") 236 | sub := &spb.SubState{ 237 | ID: 10, 238 | ClientID: "me", 239 | Inbox: "ibx", 240 | AckInbox: "ackibx", 241 | AckWaitInSecs: 10, 242 | } 243 | if err := cs.Subs.CreateSub(sub); err != nil { 244 | t.Fatalf("Error creating sub: %v", err) 245 | } 246 | if sub.ID != 10 { 247 | t.Fatalf("Store did not use the provided sub ID, expected 10, got %v", sub.ID) 248 | } 249 | // Store another with different sub.ID 250 | newSub := *sub 251 | newSub.ID = 11 252 | if err := cs.Subs.CreateSub(&newSub); err != nil { 253 | t.Fatalf("Error creating sub: %v", err) 254 | } 255 | if newSub.ID != 11 { 256 | t.Fatalf("Store did not use the provided sub ID, expected 11, got %v", newSub.ID) 257 | } 258 | 259 | // Now if we call with one of existing ID it should not count toward max subs. 260 | newSub2 := *sub 261 | newSub2.ID = 10 262 | if err := cs.Subs.CreateSub(&newSub2); err != nil { 263 | t.Fatalf("Error creating sub: %v", err) 264 | } 265 | if newSub2.ID != 10 { 266 | t.Fatalf("Store did not use the provided sub ID, expected 10, got %v", newSub2.ID) 267 | } 268 | 269 | // Now, a new one should fail. 270 | newSub3 := *sub 271 | newSub3.ID = 12 272 | if err := cs.Subs.CreateSub(&newSub3); err != ErrTooManySubs { 273 | t.Fatalf("Expected too many subs error, got %v", err) 274 | } 275 | 276 | // Delete the first (ID=10) 277 | if err := cs.Subs.DeleteSub(10); err != nil { 278 | t.Fatalf("Error deleting sub: %v", err) 279 | } 280 | 281 | // Check that we support sub.ID == 0 and take the max+1. 282 | // So if we change newSub3.ID to 0, after create it should be 283 | // set to 12 (since last max is 11) 284 | newSub3.ID = 0 285 | if err := cs.Subs.CreateSub(&newSub3); err != nil { 286 | t.Fatalf("Error creating sub: %v", err) 287 | } 288 | if newSub3.ID != 12 { 289 | t.Fatalf("Expected newSub3.ID to be 12, got %v", newSub3.ID) 290 | } 291 | } 292 | 293 | func TestRSFileAutoSync(t *testing.T) { 294 | cleanupRaftDatastore(t) 295 | defer cleanupRaftDatastore(t) 296 | 297 | limits := testDefaultStoreLimits 298 | fs, err := NewFileStore(testLogger, testRSDefaultDatastore, &limits, AutoSync(15*time.Millisecond)) 299 | if err != nil { 300 | t.Fatalf("Error creating store: %v", err) 301 | } 302 | 303 | s := NewRaftStore(testLogger, fs, &limits) 304 | defer s.Close() 305 | 306 | info := testDefaultServerInfo 307 | info.ClusterID = "testRaftStore" 308 | if err := s.Init(&info); err != nil { 309 | t.Fatalf("Error on init: %v", err) 310 | } 311 | 312 | // Add some state 313 | cs := storeCreateChannel(t, s, "foo") 314 | storeMsg(t, cs, "foo", 1, []byte("msg")) 315 | storeSub(t, cs, "foo") 316 | 317 | // Wait for auto sync to kick in 318 | time.Sleep(50 * time.Millisecond) 319 | 320 | // Server should not have panic'ed. 321 | } 322 | -------------------------------------------------------------------------------- /test/certs/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEkDCCA3igAwIBAgIUSZwW7btc9EUbrMWtjHpbM0C2bSEwDQYJKoZIhvcNAQEL 3 | BQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM 4 | B1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl 5 | IEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw 6 | MjMwMlowcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV 7 | BAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmlj 8 | YXRlIEF1dGhvcml0eSAyMDIyLTA4LTI3MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 9 | MIIBCgKCAQEAqilVqyY8rmCpTwAsLF7DEtWEq37KbljBWVjmlp2Wo6TgMd3b537t 10 | 6iO8+SbI8KH75i63RcxV3Uzt1/L9Yb6enDXF52A/U5ugmDhaa+Vsoo2HBTbCczmp 11 | qndp7znllQqn7wNLv6aGSvaeIUeYS5Dmlh3kt7Vqbn4YRANkOUTDYGSpMv7jYKSu 12 | 1ee05Rco3H674zdwToYto8L8V7nVMrky42qZnGrJTaze+Cm9tmaIyHCwUq362CxS 13 | dkmaEuWx11MOIFZvL80n7ci6pveDxe5MIfwMC3/oGn7mbsSqidPMcTtjw6ey5NEu 14 | Z0UrC/2lL1FtF4gnVMKUSaEhU2oKjj0ZAQIDAQABo4IBHjCCARowHQYDVR0OBBYE 15 | FP7Pfz4u7sSt6ltviEVsx4hIFIs6MIGuBgNVHSMEgaYwgaOAFP7Pfz4u7sSt6ltv 16 | iEVsx4hIFIs6oXWkczBxMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5p 17 | YTEQMA4GA1UECgwHU3luYWRpYTEQMA4GA1UECwwHbmF0cy5pbzEpMCcGA1UEAwwg 18 | Q2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMjItMDgtMjeCFEmcFu27XPRFG6zFrYx6 19 | WzNAtm0hMAwGA1UdEwQFMAMBAf8wOgYJYIZIAYb4QgENBC0WK25hdHMuaW8gbmF0 20 | cy1zZXJ2ZXIgdGVzdC1zdWl0ZSB0cmFuc2llbnQgQ0EwDQYJKoZIhvcNAQELBQAD 21 | ggEBAHDCHLQklYZlnzHDaSwxgGSiPUrCf2zhk2DNIYSDyBgdzrIapmaVYQRrCBtA 22 | j/4jVFesgw5WDoe4TKsyha0QeVwJDIN8qg2pvpbmD8nOtLApfl0P966vcucxDwqO 23 | dQWrIgNsaUdHdwdo0OfvAlTfG0v/y2X0kbL7h/el5W9kWpxM/rfbX4IHseZL2sLq 24 | FH69SN3FhMbdIm1ldrcLBQVz8vJAGI+6B9hSSFQWljssE0JfAX+8VW/foJgMSx7A 25 | vBTq58rLkAko56Jlzqh/4QT+ckayg9I73v1Q5/44jP1mHw35s5ZrzpDQt2sVv4l5 26 | lwRPJFXMwe64flUs9sM+/vqJaIY= 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /test/certs/client-cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 5 | 38:4c:16:24:9b:04:1c:b3:db:e0:4c:3c:ed:b7:40:7d:68:b5:fa:1f 6 | Signature Algorithm: sha256WithRSAEncryption 7 | Issuer: C=US, ST=California, O=Synadia, OU=nats.io, CN=Certificate Authority 2022-08-27 8 | Validity 9 | Not Before: Aug 27 20:23:02 2022 GMT 10 | Not After : Aug 24 20:23:02 2032 GMT 11 | Subject: C=US, ST=California, O=Synadia, OU=nats.io, CN=localhost 12 | Subject Public Key Info: 13 | Public Key Algorithm: rsaEncryption 14 | RSA Public-Key: (2048 bit) 15 | Modulus: 16 | 00:ac:9c:3e:9d:3b:7a:12:56:85:78:ca:df:9c:fc: 17 | 0c:7e:5e:f2:4f:22:33:46:81:38:53:d7:a7:25:8f: 18 | d7:ee:16:13:e2:67:49:88:f6:94:99:f0:a9:a6:db: 19 | fe:7a:17:c9:e3:df:31:73:71:38:70:3a:96:1e:99: 20 | 7b:5d:07:e3:63:e4:e8:bf:99:f7:3d:5c:27:f5:b7: 21 | 37:29:da:ee:82:80:00:d4:c8:d3:1b:36:0d:8b:d3: 22 | 8a:9b:8e:12:a1:4d:0c:c5:22:f8:56:3b:6a:1a:fb: 23 | e9:3d:08:1e:13:7f:55:6e:2e:65:93:9a:90:54:03: 24 | 6d:0d:e6:44:d6:f7:c0:d7:d8:e1:c7:1e:c2:9b:a3: 25 | 6e:88:f1:7c:58:08:a2:9f:13:cc:5b:b9:11:2c:1d: 26 | 23:6f:3a:ae:47:9a:0f:6a:ce:e5:80:34:09:e6:e3: 27 | fd:76:4a:cf:5a:18:bb:9c:c5:c1:74:49:67:77:1b: 28 | ba:28:86:31:a6:fc:12:af:4a:85:1b:73:5b:f4:d6: 29 | 42:ff:0c:1c:49:e7:31:f2:5a:2a:1e:cd:87:cb:22: 30 | ff:70:1c:48:ed:ba:e0:be:f0:bc:9e:e0:dc:59:db: 31 | a5:74:25:58:b3:61:04:f6:33:28:6b:07:25:60:0f: 32 | 72:93:16:6c:9f:b0:ad:4a:18:f7:9e:29:1e:b7:61: 33 | 34:17 34 | Exponent: 65537 (0x10001) 35 | X509v3 extensions: 36 | X509v3 Basic Constraints: 37 | CA:FALSE 38 | Netscape Comment: 39 | nats.io nats-server test-suite certificate 40 | X509v3 Subject Key Identifier: 41 | 1F:14:EF:2B:53:AB:28:4A:93:42:98:AE:85:06:0F:B4:7D:DC:36:AE 42 | X509v3 Authority Key Identifier: 43 | keyid:FE:CF:7F:3E:2E:EE:C4:AD:EA:5B:6F:88:45:6C:C7:88:48:14:8B:3A 44 | DirName:/C=US/ST=California/O=Synadia/OU=nats.io/CN=Certificate Authority 2022-08-27 45 | serial:49:9C:16:ED:BB:5C:F4:45:1B:AC:C5:AD:8C:7A:5B:33:40:B6:6D:21 46 | 47 | X509v3 Subject Alternative Name: 48 | DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, email:derek@nats.io 49 | Netscape Cert Type: 50 | SSL Client 51 | X509v3 Key Usage: 52 | Digital Signature, Key Encipherment 53 | X509v3 Extended Key Usage: 54 | TLS Web Client Authentication 55 | Signature Algorithm: sha256WithRSAEncryption 56 | 60:43:0b:c6:11:0b:96:ae:03:dc:77:26:9a:4a:bd:6a:d7:03: 57 | ec:43:16:2d:ba:8c:e5:50:fa:57:a9:1f:2f:a4:15:c3:a8:13: 58 | b9:d3:59:2a:97:7c:ae:ce:a9:f8:44:e4:97:ee:7d:09:dc:74: 59 | 38:80:94:cf:47:e0:84:52:2a:91:44:8a:85:55:da:42:6a:f1: 60 | 91:1a:6e:5a:63:e6:0b:61:3c:0d:b0:aa:17:b8:77:94:32:20: 61 | 4d:20:8f:84:56:64:ae:ef:d8:8d:42:b5:52:4d:b0:1c:46:97: 62 | bc:4c:77:8c:3f:a3:73:43:87:27:71:62:e7:fe:02:de:a1:27: 63 | 77:be:86:29:8f:62:a1:d9:e7:ea:61:33:73:f4:1f:0a:12:14: 64 | 68:eb:7d:8c:71:5b:42:e7:48:10:c9:df:30:3b:5b:eb:69:29: 65 | b6:95:bc:09:fc:01:b0:be:fc:9f:ee:c4:f3:df:a0:01:c5:68: 66 | 20:f5:2f:f8:e7:1c:a5:4c:a8:a8:a2:20:a1:d2:0f:f6:f6:c4: 67 | 0d:f5:26:fd:ea:8b:b5:06:a9:9e:17:35:47:f7:fd:6e:78:3d: 68 | 5f:7a:87:ed:21:b2:4e:e9:6a:d1:d9:ed:0e:cf:43:61:83:7c: 69 | fe:0d:b1:ad:ff:fa:2d:2b:36:9d:99:9c:20:48:21:0d:36:c8: 70 | dd:b6:0a:d8 71 | -----BEGIN CERTIFICATE----- 72 | MIIE5zCCA8+gAwIBAgIUOEwWJJsEHLPb4Ew87bdAfWi1+h8wDQYJKoZIhvcNAQEL 73 | BQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM 74 | B1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl 75 | IEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw 76 | MjMwMlowWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV 77 | BAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xEjAQBgNVBAMMCWxvY2FsaG9z 78 | dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKycPp07ehJWhXjK35z8 79 | DH5e8k8iM0aBOFPXpyWP1+4WE+JnSYj2lJnwqabb/noXyePfMXNxOHA6lh6Ze10H 80 | 42Pk6L+Z9z1cJ/W3Nyna7oKAANTI0xs2DYvTipuOEqFNDMUi+FY7ahr76T0IHhN/ 81 | VW4uZZOakFQDbQ3mRNb3wNfY4ccewpujbojxfFgIop8TzFu5ESwdI286rkeaD2rO 82 | 5YA0Cebj/XZKz1oYu5zFwXRJZ3cbuiiGMab8Eq9KhRtzW/TWQv8MHEnnMfJaKh7N 83 | h8si/3AcSO264L7wvJ7g3FnbpXQlWLNhBPYzKGsHJWAPcpMWbJ+wrUoY954pHrdh 84 | NBcCAwEAAaOCAYwwggGIMAkGA1UdEwQCMAAwOQYJYIZIAYb4QgENBCwWKm5hdHMu 85 | aW8gbmF0cy1zZXJ2ZXIgdGVzdC1zdWl0ZSBjZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU 86 | HxTvK1OrKEqTQpiuhQYPtH3cNq4wga4GA1UdIwSBpjCBo4AU/s9/Pi7uxK3qW2+I 87 | RWzHiEgUizqhdaRzMHExCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh 88 | MRAwDgYDVQQKDAdTeW5hZGlhMRAwDgYDVQQLDAduYXRzLmlvMSkwJwYDVQQDDCBD 89 | ZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAyMi0wOC0yN4IUSZwW7btc9EUbrMWtjHpb 90 | M0C2bSEwOwYDVR0RBDQwMoIJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAA 91 | AAABgQ1kZXJla0BuYXRzLmlvMBEGCWCGSAGG+EIBAQQEAwIHgDALBgNVHQ8EBAMC 92 | BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBAGBDC8YR 93 | C5auA9x3JppKvWrXA+xDFi26jOVQ+lepHy+kFcOoE7nTWSqXfK7OqfhE5JfufQnc 94 | dDiAlM9H4IRSKpFEioVV2kJq8ZEablpj5gthPA2wqhe4d5QyIE0gj4RWZK7v2I1C 95 | tVJNsBxGl7xMd4w/o3NDhydxYuf+At6hJ3e+himPYqHZ5+phM3P0HwoSFGjrfYxx 96 | W0LnSBDJ3zA7W+tpKbaVvAn8AbC+/J/uxPPfoAHFaCD1L/jnHKVMqKiiIKHSD/b2 97 | xA31Jv3qi7UGqZ4XNUf3/W54PV96h+0hsk7patHZ7Q7PQ2GDfP4Nsa3/+i0rNp2Z 98 | nCBIIQ02yN22Ctg= 99 | -----END CERTIFICATE----- 100 | -------------------------------------------------------------------------------- /test/certs/client-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCsnD6dO3oSVoV4 3 | yt+c/Ax+XvJPIjNGgThT16clj9fuFhPiZ0mI9pSZ8Kmm2/56F8nj3zFzcThwOpYe 4 | mXtdB+Nj5Oi/mfc9XCf1tzcp2u6CgADUyNMbNg2L04qbjhKhTQzFIvhWO2oa++k9 5 | CB4Tf1VuLmWTmpBUA20N5kTW98DX2OHHHsKbo26I8XxYCKKfE8xbuREsHSNvOq5H 6 | mg9qzuWANAnm4/12Ss9aGLucxcF0SWd3G7oohjGm/BKvSoUbc1v01kL/DBxJ5zHy 7 | WioezYfLIv9wHEjtuuC+8Lye4NxZ26V0JVizYQT2MyhrByVgD3KTFmyfsK1KGPee 8 | KR63YTQXAgMBAAECggEBAKc6FHt2NPTxOAxn2C6aDmycBftesfiblnu8EWaVrmgu 9 | oYMV+CsmYZ+mhmZu+mNFCsam5JzoUvp/+BKbNeZSjx2nl0qRmvOqhdhLcbkuLybl 10 | ZmjAS64wNv2Bq+a6xRfaswWGtLuugkS0TCph4+mV0qmVb7mJ5ExQqWXu8kCl9QHn 11 | uKacp1wVFok9rmEI+byL1+Z01feKrkf/hcF6dk62U7zHNPajViJFTDww7hiHyfUH 12 | 6qsxIe1UWSNKtE61haEHkzqbDIDAy79jX4t3JobLToeVNCbJ7BSPf2IQSPJxELVL 13 | sidIJhndEjsbDR2CLpIF/EjsiSIaP7jh2zC9fxFpgSkCgYEA1qH0PH1JD5FqRV/p 14 | n9COYa6EifvSymGo4u/2FHgtX7wNSIQvqAVXenrQs41mz9E65womeqFXT/AZglaM 15 | 1PEjjwcFlDuLvUEYYJNgdXrIC515ZXS6TdvJ0JpQJLx28GzZ7h31tZXfwn68C3/i 16 | UGEHp+nN1BfBBQnsqvmGFFvHZFUCgYEAzeDlZHHijBlgHU+kGzKm7atJfAGsrv6/ 17 | tw7CIMEsL+z/y7pl3nwDLdZF+mLIvGuKlwIRajEzbYcEuVymCyG2/SmPMQEUf6j+ 18 | C1OmorX9CW8OwHmVCajkIgKn0ICFsF9iFv6aYZmm1kG48AIuYiQ7HOvY/MlilqFs 19 | 1p8sw6ZpQrsCgYEAj7Z9fQs+omfxymYAXnwc+hcKtAGkENL3bIzULryRVSrrkgTA 20 | jDaXbnFR0Qf7MWedkxnezfm+Js5TpkwhnGuiLaC8AZclaCFwGypTShZeYDifEmno 21 | XT2vkjfhNdfjo/Ser6vr3BxwaSDG9MQ6Wyu9HpeUtFD7c05D4++T8YnKpskCgYEA 22 | pCkcoIAStcWSFy0m3K0B3+dBvAiVyh/FfNDeyEFf24Mt4CPsEIBwBH+j4ugbyeoy 23 | YwC6JCPBLyeHA8q1d5DVmX4m+Fs1HioBD8UOzRUyA/CzIZSQ21f5OIlHiIDCmQUl 24 | cNJpBUQAfT2AmpgSphzfqcsBhWeLHjLvVx8rEYLC0fsCgYAiHdPZ3C0f7rWZP93N 25 | gY4DuldiO4d+KVsWAdBxeNgPznisUI7/ZZ/9NvCxGvA5NynyZr0qlpiKzVvtFJG8 26 | 1ZPUuFFRMAaWn9h5C+CwMPgk65tFC6lw/el0hpmcocSXVdiJEbkV0rnv9iGh0CYX 27 | HMACGrYlyZdDYM0CH/JAM+K/QQ== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/certs/server-cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 5 | 1d:d9:1f:06:dd:fd:90:26:4e:27:ea:2e:01:4b:31:e6:d2:49:31:1f 6 | Signature Algorithm: sha256WithRSAEncryption 7 | Issuer: C=US, ST=California, O=Synadia, OU=nats.io, CN=Certificate Authority 2022-08-27 8 | Validity 9 | Not Before: Aug 27 20:23:02 2022 GMT 10 | Not After : Aug 24 20:23:02 2032 GMT 11 | Subject: C=US, ST=California, O=Synadia, OU=nats.io, CN=localhost 12 | Subject Public Key Info: 13 | Public Key Algorithm: rsaEncryption 14 | RSA Public-Key: (2048 bit) 15 | Modulus: 16 | 00:e6:fb:47:65:cd:c9:a2:2d:af:8b:cd:d5:6a:79: 17 | 54:3c:07:5f:eb:5a:71:2b:2b:e5:6f:be:31:fb:16: 18 | 65:68:76:0e:59:e7:e4:57:ca:88:e9:77:d6:41:ad: 19 | 57:7a:42:b2:d2:54:c4:0f:7c:5b:c1:bc:61:97:e3: 20 | 22:3a:3e:1e:4a:5d:47:9f:6b:7d:6f:34:e3:8c:86: 21 | 9d:85:19:29:9a:11:58:44:4c:a1:90:d3:14:61:e1: 22 | 57:da:01:ea:ce:3f:90:ae:9e:5d:13:6d:2c:89:ca: 23 | 39:15:6b:b6:9e:32:d7:2a:4c:48:85:2f:b0:1e:d8: 24 | 4b:62:32:14:eb:32:b6:29:04:34:3c:af:39:b6:8b: 25 | 52:32:4d:bf:43:5f:9b:fb:0d:43:a6:ad:2c:a7:41: 26 | 29:55:c9:70:b3:b5:15:46:34:bf:e4:1e:52:2d:a4: 27 | 49:2e:d5:21:ed:fc:00:f7:a2:0b:bc:12:0a:90:64: 28 | 50:7c:c5:14:70:f5:fb:9b:62:08:78:43:49:31:f3: 29 | 47:b8:93:d4:2d:4c:a9:dc:17:70:76:34:66:ff:65: 30 | c1:39:67:e9:a6:1c:80:6a:f0:9d:b3:28:c8:a3:3a: 31 | b7:5d:de:6e:53:6d:09:b3:0d:b1:13:10:e8:ec:e0: 32 | bd:5e:a1:94:4b:70:bf:dc:bd:8b:b9:82:65:dd:af: 33 | 81:7b 34 | Exponent: 65537 (0x10001) 35 | X509v3 extensions: 36 | X509v3 Basic Constraints: 37 | CA:FALSE 38 | Netscape Comment: 39 | nats.io nats-server test-suite certificate 40 | X509v3 Subject Key Identifier: 41 | 2B:8C:A3:8B:DB:DB:5C:CE:18:DB:F6:A8:31:4E:C2:3E:EE:D3:40:7E 42 | X509v3 Authority Key Identifier: 43 | keyid:FE:CF:7F:3E:2E:EE:C4:AD:EA:5B:6F:88:45:6C:C7:88:48:14:8B:3A 44 | DirName:/C=US/ST=California/O=Synadia/OU=nats.io/CN=Certificate Authority 2022-08-27 45 | serial:49:9C:16:ED:BB:5C:F4:45:1B:AC:C5:AD:8C:7A:5B:33:40:B6:6D:21 46 | 47 | X509v3 Subject Alternative Name: 48 | DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1 49 | Netscape Cert Type: 50 | SSL Client, SSL Server 51 | X509v3 Key Usage: 52 | Digital Signature, Key Encipherment 53 | X509v3 Extended Key Usage: 54 | TLS Web Server Authentication, Netscape Server Gated Crypto, Microsoft Server Gated Crypto, TLS Web Client Authentication 55 | Signature Algorithm: sha256WithRSAEncryption 56 | 54:49:34:2b:38:d1:aa:3b:43:60:4c:3f:6a:f8:74:ca:49:53: 57 | a1:af:12:d3:a8:17:90:7b:9d:a3:69:13:6e:da:2c:b7:61:31: 58 | ac:eb:00:93:92:fc:0c:10:d4:18:a0:16:61:94:4b:42:cb:eb: 59 | 7a:f6:80:c6:45:c0:9c:09:aa:a9:48:e8:36:e3:c5:be:36:e0: 60 | e9:78:2a:bb:ab:64:9b:20:eb:e6:0f:63:2b:59:c3:58:0b:3a: 61 | 84:15:04:c1:7e:12:03:1b:09:25:8d:4c:03:e8:18:26:c0:6c: 62 | b7:90:b1:fd:bc:f1:cf:d0:d5:4a:03:15:71:0c:7d:c1:76:87: 63 | 92:f1:3e:bc:75:51:5a:c4:36:a4:ff:91:98:df:33:5d:a7:38: 64 | de:50:29:fd:0f:c8:55:e6:8f:24:c2:2e:98:ab:d9:5d:65:2f: 65 | 50:cc:25:f6:84:f2:21:2e:5e:76:d0:86:1e:69:8b:cb:8a:3a: 66 | 2d:79:21:5e:e7:f7:2d:06:18:a1:13:cb:01:c3:46:91:2a:de: 67 | b4:82:d7:c3:62:6f:08:a1:d5:90:19:30:9d:64:8e:e4:f8:ba: 68 | 4f:2f:ba:13:b4:a3:9f:d1:d5:77:64:8a:3e:eb:53:c5:47:ac: 69 | ab:3e:0e:7a:9b:a6:f4:48:25:66:eb:c7:4c:f9:50:24:eb:71: 70 | e0:75:ae:e6 71 | -----BEGIN CERTIFICATE----- 72 | MIIE+TCCA+GgAwIBAgIUHdkfBt39kCZOJ+ouAUsx5tJJMR8wDQYJKoZIhvcNAQEL 73 | BQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM 74 | B1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl 75 | IEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw 76 | MjMwMlowWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV 77 | BAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xEjAQBgNVBAMMCWxvY2FsaG9z 78 | dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOb7R2XNyaItr4vN1Wp5 79 | VDwHX+tacSsr5W++MfsWZWh2Dlnn5FfKiOl31kGtV3pCstJUxA98W8G8YZfjIjo+ 80 | HkpdR59rfW8044yGnYUZKZoRWERMoZDTFGHhV9oB6s4/kK6eXRNtLInKORVrtp4y 81 | 1ypMSIUvsB7YS2IyFOsytikENDyvObaLUjJNv0Nfm/sNQ6atLKdBKVXJcLO1FUY0 82 | v+QeUi2kSS7VIe38APeiC7wSCpBkUHzFFHD1+5tiCHhDSTHzR7iT1C1MqdwXcHY0 83 | Zv9lwTln6aYcgGrwnbMoyKM6t13eblNtCbMNsRMQ6OzgvV6hlEtwv9y9i7mCZd2v 84 | gXsCAwEAAaOCAZ4wggGaMAkGA1UdEwQCMAAwOQYJYIZIAYb4QgENBCwWKm5hdHMu 85 | aW8gbmF0cy1zZXJ2ZXIgdGVzdC1zdWl0ZSBjZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU 86 | K4yji9vbXM4Y2/aoMU7CPu7TQH4wga4GA1UdIwSBpjCBo4AU/s9/Pi7uxK3qW2+I 87 | RWzHiEgUizqhdaRzMHExCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh 88 | MRAwDgYDVQQKDAdTeW5hZGlhMRAwDgYDVQQLDAduYXRzLmlvMSkwJwYDVQQDDCBD 89 | ZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAyMi0wOC0yN4IUSZwW7btc9EUbrMWtjHpb 90 | M0C2bSEwLAYDVR0RBCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAA 91 | AAABMBEGCWCGSAGG+EIBAQQEAwIGwDALBgNVHQ8EBAMCBaAwNAYDVR0lBC0wKwYI 92 | KwYBBQUHAwEGCWCGSAGG+EIEAQYKKwYBBAGCNwoDAwYIKwYBBQUHAwIwDQYJKoZI 93 | hvcNAQELBQADggEBAFRJNCs40ao7Q2BMP2r4dMpJU6GvEtOoF5B7naNpE27aLLdh 94 | MazrAJOS/AwQ1BigFmGUS0LL63r2gMZFwJwJqqlI6Dbjxb424Ol4KrurZJsg6+YP 95 | YytZw1gLOoQVBMF+EgMbCSWNTAPoGCbAbLeQsf288c/Q1UoDFXEMfcF2h5LxPrx1 96 | UVrENqT/kZjfM12nON5QKf0PyFXmjyTCLpir2V1lL1DMJfaE8iEuXnbQhh5pi8uK 97 | Oi15IV7n9y0GGKETywHDRpEq3rSC18Nibwih1ZAZMJ1kjuT4uk8vuhO0o5/R1Xdk 98 | ij7rU8VHrKs+DnqbpvRIJWbrx0z5UCTrceB1ruY= 99 | -----END CERTIFICATE----- 100 | -------------------------------------------------------------------------------- /test/certs/server-key-noip.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCt8Ic/MmaHejGb 3 | ylQKrqYayiXVxfxJayEL3qcVyJw8zUEdMiV3aHuD6F0Uei4L6kGRpCDsIBcPy41M 4 | G4ig0ndGZX7RoOZMS8aMOaGzWzRXyKEQDBNUOnSQezu62kFigfXctXNsgzj0oVKr 5 | vcKVPnn/r6Su39YR2SkguLQV4zKTXDbOVrQBAqFFMaOhHuq4xAEEVxFE9FXq4q5o 6 | CHCFwFv/ur/ei7yhxgOiL4rrnrd5OmdqsHDT6AinEiTVu1eIcjfI5i7bh+AqcRos 7 | kJyIKQx1KITWf3UtUAg2K8/zujNyHnoH2yDamDs5hpZM4kpCYRqbC2dNbRPRn0Df 8 | EseNnVBpAgMBAAECggEAcmiqXRwmqmfqZ4Ge4+Pap/ZdCo6OkjAf7XHHTyHD+o47 9 | jRul3zPfQnU9fDGdRgMQm95sNUQqRx5pUy0tIjMtdyVdVD9UG80fzK4/uPx9olv5 10 | 7Nc0g4trjnkwYYgbx9KZyFGlmTN67BWMjiBj88zDbDW4ybm7UcQYNEipU1g8tQW1 11 | tUwcZ1oahXfzO75vcMqDVlS2IE0s0AD9sh+AaJIwxV9kSLNjlSwkpsH6PBKKB/3r 12 | WvG2p6Og1whdQ54PGADUVSx1yWFyXQDeygqLmryEWaHJQz1jt7bvaaAMy2PTdwVf 13 | A5LVG3VHkoQOBv8imtpCbU2J7zAk9ypDuRUlpa8h/QKBgQDdCCCbV02BhrqDYchm 14 | ojB95Vx8KtvQdXhvsxShxyuIktuB7W+NnheBmLY0TNcYSQyzithCUBhtmyaC5S4f 15 | dHmT52e7HS0xaL9r9BhAQrtWReMcplKB1IIXtdYXEY3qOjZMxX3seJo0iBWS3hMH 16 | EG6tC6tlr5ZXOKJOrBMGuMgplwKBgQDJdSYkC3AX2p+4BNf3hgQyzotuSVSbx/zu 17 | 0ZHhi8Wp7yF49c8+9+ahO9AMrVM0ZSh2buznfF46FNC/C55M7a9Rn60sFQQ16b5L 18 | rJTzlPoUGTnPLt8C3TdMIFg/5cAW6ZgZWNlU3aVU0W34NVh/H2m/M72tGrk250zs 19 | YhZ8/RGV/wKBgQCKlMfs3YXoyhIywaImR1Zj+ORNrYl4X86NKhirffbbgEhEZBvn 20 | DNHsHVVP4UWTImnmQA1rNlC6l+ZDd3G9owd/Jj0xYg+txOEPzFFQKQbQBq1ojxd3 21 | 80dFmmqKuCTkUG8vHzvegZcdjJ0KIlaHvVPHB2QFM1vtf8Kz1MtxEXXeLQKBgDn0 22 | Bm3WEH/8N3gzhIFDP0/yVO/8DmfmByAYj5PHpqw1C3cFl4HwxJrbXwVWkxn+g75W 23 | OLZ684xX0pky2W4d7hJYEfQdc6GixUh1tD/COpKvkw7D2Am146N1po1zJWgx+LxJ 24 | 7/NW86nLuYvupK+lNMF5O/ZhOqjNrzZNHVUFZBq3AoGAPwixh7/ZMX6mmm8foImh 25 | qibytx72gl1jhHWSaX3rwrSOO9dxO2rlI7LOZQrarU632Y9KMkP3HNbBHPRkA4MI 26 | 6I9wqawRzGjcpeXIMlPzOHDHYLyrTpEzo8nrSNk/cM8P4RxE12FqySzQIkiN06J7 27 | AxJ7hVqtX6wZIoqoOa9aK1E= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/certs/server-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDm+0dlzcmiLa+L 3 | zdVqeVQ8B1/rWnErK+VvvjH7FmVodg5Z5+RXyojpd9ZBrVd6QrLSVMQPfFvBvGGX 4 | 4yI6Ph5KXUefa31vNOOMhp2FGSmaEVhETKGQ0xRh4VfaAerOP5Cunl0TbSyJyjkV 5 | a7aeMtcqTEiFL7Ae2EtiMhTrMrYpBDQ8rzm2i1IyTb9DX5v7DUOmrSynQSlVyXCz 6 | tRVGNL/kHlItpEku1SHt/AD3ogu8EgqQZFB8xRRw9fubYgh4Q0kx80e4k9QtTKnc 7 | F3B2NGb/ZcE5Z+mmHIBq8J2zKMijOrdd3m5TbQmzDbETEOjs4L1eoZRLcL/cvYu5 8 | gmXdr4F7AgMBAAECggEBAK4sr3MiEbjcsHJAvXyzjwRRH1Bu+8VtLW7swe2vvrpd 9 | w4aiKXrV/BXpSsRtvPgxkXyvdMSkpuBZeFI7cVTwAJFc86RQPt77x9bwr5ltFwTZ 10 | rXCbRH3b3ZPNhByds3zhS+2Q92itu5cPyanQdn2mor9/lHPyOOGZgobCcynELL6R 11 | wRElkeDyf5ODuWEd7ADC5IFyZuwb3azNVexIK+0yqnMmv+QzEW3hsycFmFGAeB7v 12 | MIMjb2BhLrRr6Y5Nh+k58yM5DCf9h/OJhDpeXwLkxyK4BFg+aZffEbUX0wHDMR7f 13 | /nMv1g6cKvDWiLU8xLzez4t2qNIBNdxw5ZSLyQRRolECgYEA+ySTKrBAqI0Uwn8H 14 | sUFH95WhWUXryeRyGyQsnWAjZGF1+d67sSY2un2W6gfZrxRgiNLWEFq9AaUs0MuH 15 | 6syF4Xwx/aZgU/gvsGtkgzuKw1bgvekT9pS/+opmHRCZyQAFEHj0IEpzyB6rW1u/ 16 | LdlR3ShEENnmXilFv/uF/uXP5tMCgYEA63LiT0w46aGPA/E+aLRWU10c1eZ7KdhR 17 | c3En6zfgIxgFs8J38oLdkOR0CF6T53DSuvGR/OprVKdlnUhhDxBgT1oQjK2GlhPx 18 | JV5uMvarJDJxAwsF+7T4H2QtZ00BtEfpyp790+TlypSG1jo/BnSMmX2uEbV722lY 19 | hzINLY49obkCgYBEpN2YyG4T4+PtuXznxRkfogVk+kiVeVx68KtFJLbnw//UGT4i 20 | EHjbBmLOevDT+vTb0QzzkWmh3nzeYRM4aUiatjCPzP79VJPsW54whIDMHZ32KpPr 21 | TQMgPt3kSdpO5zN7KiRIAzGcXE2n/e7GYGUQ1uWr2XMu/4byD5SzdCscQwJ/Ymii 22 | LoKtRvk/zWYHr7uwWSeR5dVvpQ3E/XtONAImrIRd3cRqXfJUqTrTRKxDJXkCmyBc 23 | 5FkWg0t0LUkTSDiQCJqcUDA3EINFR1kwthxja72pfpwc5Be/nV9BmuuUysVD8myB 24 | qw8A/KsXsHKn5QrRuVXOa5hvLEXbuqYw29mX6QKBgDGDzIzpR9uPtBCqzWJmc+IJ 25 | z4m/1NFlEz0N0QNwZ/TlhyT60ytJNcmW8qkgOSTHG7RDueEIzjQ8LKJYH7kXjfcF 26 | 6AJczUG5PQo9cdJKo9JP3e1037P/58JpLcLe8xxQ4ce03zZpzhsxR2G/tz8DstJs 27 | b8jpnLyqfGrcV2feUtIZ 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/certs/server-noip.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 5 | 1d:5c:7c:59:0c:cd:27:83:dd:97:64:53:b0:44:3c:b4:5b:d4:fc:d1 6 | Signature Algorithm: sha256WithRSAEncryption 7 | Issuer: C=US, ST=California, O=Synadia, OU=nats.io, CN=Certificate Authority 2022-08-27 8 | Validity 9 | Not Before: Aug 27 20:23:02 2022 GMT 10 | Not After : Aug 24 20:23:02 2032 GMT 11 | Subject: C=US, ST=California, O=Synadia, OU=nats.io, CN=localhost 12 | Subject Public Key Info: 13 | Public Key Algorithm: rsaEncryption 14 | RSA Public-Key: (2048 bit) 15 | Modulus: 16 | 00:ad:f0:87:3f:32:66:87:7a:31:9b:ca:54:0a:ae: 17 | a6:1a:ca:25:d5:c5:fc:49:6b:21:0b:de:a7:15:c8: 18 | 9c:3c:cd:41:1d:32:25:77:68:7b:83:e8:5d:14:7a: 19 | 2e:0b:ea:41:91:a4:20:ec:20:17:0f:cb:8d:4c:1b: 20 | 88:a0:d2:77:46:65:7e:d1:a0:e6:4c:4b:c6:8c:39: 21 | a1:b3:5b:34:57:c8:a1:10:0c:13:54:3a:74:90:7b: 22 | 3b:ba:da:41:62:81:f5:dc:b5:73:6c:83:38:f4:a1: 23 | 52:ab:bd:c2:95:3e:79:ff:af:a4:ae:df:d6:11:d9: 24 | 29:20:b8:b4:15:e3:32:93:5c:36:ce:56:b4:01:02: 25 | a1:45:31:a3:a1:1e:ea:b8:c4:01:04:57:11:44:f4: 26 | 55:ea:e2:ae:68:08:70:85:c0:5b:ff:ba:bf:de:8b: 27 | bc:a1:c6:03:a2:2f:8a:eb:9e:b7:79:3a:67:6a:b0: 28 | 70:d3:e8:08:a7:12:24:d5:bb:57:88:72:37:c8:e6: 29 | 2e:db:87:e0:2a:71:1a:2c:90:9c:88:29:0c:75:28: 30 | 84:d6:7f:75:2d:50:08:36:2b:cf:f3:ba:33:72:1e: 31 | 7a:07:db:20:da:98:3b:39:86:96:4c:e2:4a:42:61: 32 | 1a:9b:0b:67:4d:6d:13:d1:9f:40:df:12:c7:8d:9d: 33 | 50:69 34 | Exponent: 65537 (0x10001) 35 | X509v3 extensions: 36 | X509v3 Basic Constraints: 37 | CA:FALSE 38 | Netscape Comment: 39 | nats.io nats-server test-suite certificate 40 | X509v3 Subject Key Identifier: 41 | C9:AA:3C:08:39:7E:C1:42:C0:3D:B7:2F:84:21:E7:8A:30:E7:C7:B1 42 | X509v3 Authority Key Identifier: 43 | keyid:FE:CF:7F:3E:2E:EE:C4:AD:EA:5B:6F:88:45:6C:C7:88:48:14:8B:3A 44 | DirName:/C=US/ST=California/O=Synadia/OU=nats.io/CN=Certificate Authority 2022-08-27 45 | serial:49:9C:16:ED:BB:5C:F4:45:1B:AC:C5:AD:8C:7A:5B:33:40:B6:6D:21 46 | 47 | X509v3 Subject Alternative Name: 48 | DNS:localhost 49 | Netscape Cert Type: 50 | SSL Client, SSL Server 51 | X509v3 Key Usage: 52 | Digital Signature, Key Encipherment 53 | X509v3 Extended Key Usage: 54 | TLS Web Server Authentication, Netscape Server Gated Crypto, Microsoft Server Gated Crypto, TLS Web Client Authentication 55 | Signature Algorithm: sha256WithRSAEncryption 56 | 9b:63:ae:ec:56:ec:0c:7a:d5:88:d1:0a:0a:81:29:37:4f:a6: 57 | 08:b8:78:78:23:af:5b:b7:65:61:d7:64:2a:c9:e7:a6:d2:b1: 58 | cb:36:bf:23:2e:2d:48:85:7f:16:0f:64:af:03:db:5d:0e:a7: 59 | 14:c5:f6:04:b2:6b:92:27:ba:cb:d2:13:25:a2:15:b0:8e:4a: 60 | 2d:eb:41:18:09:b1:68:d5:0f:6b:56:da:86:ed:4a:7a:29:30: 61 | 09:77:63:a4:64:3d:e3:2e:d7:6f:1a:8c:96:c9:cb:81:fe:a3: 62 | 6d:35:e3:09:ea:9b:2e:da:8c:8e:c8:c9:69:b1:83:e7:6f:2d: 63 | 5f:a1:ac:32:ae:29:57:a9:5c:9b:7d:f0:fd:47:3c:f3:6a:d0: 64 | eb:77:8d:70:06:a2:74:3d:d6:37:1e:7b:e7:d9:e4:33:c9:9d: 65 | ad:fa:24:c6:4d:e2:2c:c9:25:cb:75:be:8d:e9:83:7e:ad:db: 66 | 53:9e:97:be:d5:7f:83:90:fc:75:1d:02:29:b7:99:18:a3:39: 67 | 25:a2:54:b7:21:7d:be:0b:4c:ea:ff:80:b9:4b:5e:21:ed:25: 68 | ad:d4:62:52:59:79:83:32:df:30:a1:64:68:05:cc:35:ad:8b: 69 | d3:66:6b:b1:31:b7:b3:b2:d8:0f:5b:96:40:ef:57:1d:7f:b0: 70 | b0:f4:e9:db 71 | -----BEGIN CERTIFICATE----- 72 | MIIE4TCCA8mgAwIBAgIUHVx8WQzNJ4Pdl2RTsEQ8tFvU/NEwDQYJKoZIhvcNAQEL 73 | BQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM 74 | B1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl 75 | IEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw 76 | MjMwMlowWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV 77 | BAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xEjAQBgNVBAMMCWxvY2FsaG9z 78 | dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3whz8yZod6MZvKVAqu 79 | phrKJdXF/ElrIQvepxXInDzNQR0yJXdoe4PoXRR6LgvqQZGkIOwgFw/LjUwbiKDS 80 | d0ZlftGg5kxLxow5obNbNFfIoRAME1Q6dJB7O7raQWKB9dy1c2yDOPShUqu9wpU+ 81 | ef+vpK7f1hHZKSC4tBXjMpNcNs5WtAECoUUxo6Ee6rjEAQRXEUT0VerirmgIcIXA 82 | W/+6v96LvKHGA6Iviuuet3k6Z2qwcNPoCKcSJNW7V4hyN8jmLtuH4CpxGiyQnIgp 83 | DHUohNZ/dS1QCDYrz/O6M3IeegfbINqYOzmGlkziSkJhGpsLZ01tE9GfQN8Sx42d 84 | UGkCAwEAAaOCAYYwggGCMAkGA1UdEwQCMAAwOQYJYIZIAYb4QgENBCwWKm5hdHMu 85 | aW8gbmF0cy1zZXJ2ZXIgdGVzdC1zdWl0ZSBjZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU 86 | yao8CDl+wULAPbcvhCHnijDnx7Ewga4GA1UdIwSBpjCBo4AU/s9/Pi7uxK3qW2+I 87 | RWzHiEgUizqhdaRzMHExCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh 88 | MRAwDgYDVQQKDAdTeW5hZGlhMRAwDgYDVQQLDAduYXRzLmlvMSkwJwYDVQQDDCBD 89 | ZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAyMi0wOC0yN4IUSZwW7btc9EUbrMWtjHpb 90 | M0C2bSEwFAYDVR0RBA0wC4IJbG9jYWxob3N0MBEGCWCGSAGG+EIBAQQEAwIGwDAL 91 | BgNVHQ8EBAMCBaAwNAYDVR0lBC0wKwYIKwYBBQUHAwEGCWCGSAGG+EIEAQYKKwYB 92 | BAGCNwoDAwYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBAJtjruxW7Ax61YjR 93 | CgqBKTdPpgi4eHgjr1u3ZWHXZCrJ56bSscs2vyMuLUiFfxYPZK8D210OpxTF9gSy 94 | a5InusvSEyWiFbCOSi3rQRgJsWjVD2tW2obtSnopMAl3Y6RkPeMu128ajJbJy4H+ 95 | o2014wnqmy7ajI7IyWmxg+dvLV+hrDKuKVepXJt98P1HPPNq0Ot3jXAGonQ91jce 96 | e+fZ5DPJna36JMZN4izJJct1vo3pg36t21Oel77Vf4OQ/HUdAim3mRijOSWiVLch 97 | fb4LTOr/gLlLXiHtJa3UYlJZeYMy3zChZGgFzDWti9Nma7Ext7Oy2A9blkDvVx1/ 98 | sLD06ds= 99 | -----END CERTIFICATE----- 100 | -------------------------------------------------------------------------------- /test/configs/multi_user.conf: -------------------------------------------------------------------------------- 1 | listen: 127.0.0.1:4233 2 | http: 127.0.0.1:8233 3 | 4 | authorization { 5 | users = [ 6 | {user: alice, password: foo} 7 | {user: bob, password: bar} 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/configs/test_parse.conf: -------------------------------------------------------------------------------- 1 | streaming: { 2 | id: "me" 3 | discover_prefix: "discover" 4 | store: "file" 5 | dir: "/path/to/datastore" 6 | sd: true 7 | sv: true 8 | ns: "nats://localhost:4222" 9 | secure: true 10 | hb_interval: "10s" 11 | hb_timeout: "1s" 12 | hb_fail_count: 2 13 | ft_group: "ft" 14 | partitioning: true 15 | syslog_name: "myservice" 16 | encrypt: true 17 | encryption_cipher: "AES" 18 | encryption_key: "key" 19 | credentials: "credentials.creds" 20 | username: "user" 21 | password: "password" 22 | token: "token" 23 | nkey_seed_file: "seedfile" 24 | 25 | store_limits: { 26 | max_channels: 11 27 | max_msgs: 12 28 | max_bytes: 13 29 | max_age: "14s" 30 | max_subs: 15 31 | max_inactivity: "16s" 32 | 33 | channels: { 34 | "foo": { 35 | max_msgs: 1 36 | max_bytes: 2 37 | max_age: "3s" 38 | max_subs: 4 39 | max_inactivity: "5s" 40 | } 41 | "bar": { 42 | max_msgs: 5 43 | max_bytes: 6 44 | max_age: "7s" 45 | max_subs: 8 46 | max_inactivity: "9s" 47 | } 48 | } 49 | } 50 | 51 | tls: { 52 | client_cert: "/path/to/client/cert_file" 53 | client_key: "/path/to/client/key_file" 54 | client_ca: "/path/to/client/ca_file" 55 | server_name: "localhost" 56 | insecure: true 57 | } 58 | 59 | file: { 60 | compact: true 61 | compact_frag: 1 62 | compact_interval: 2 63 | compact_min_size: 3 64 | buffer_size: 4 65 | crc: true 66 | crc_poly: 5 67 | sync: true 68 | cache: true 69 | slice_max_msgs: 6 70 | slice_max_bytes: 7 71 | slice_max_age: "8s" 72 | slice_archive_script: "myArchiveScript" 73 | fds_limit: 8 74 | parallel_recovery: 9 75 | read_buffer_size: 10 76 | auto_sync: "2m" 77 | } 78 | 79 | cluster: { 80 | node_id: "a" 81 | bootstrap: true 82 | peers: ["b", "c"] 83 | log_path: "/path/to/log" 84 | log_cache_size: 1024 85 | log_snapshots: 1 86 | trailing_logs: 256 87 | sync: true 88 | proceed_on_restore_failure: true 89 | raft_logging: true 90 | raft_heartbeat_timeout: "1s" 91 | raft_election_timeout: "1s" 92 | raft_lease_timeout: "500ms" 93 | raft_commit_timeout: "50ms" 94 | allow_add_remove_node: true 95 | bolt_free_list_sync: true 96 | bolt_free_list_map: true 97 | nodes_connections: true 98 | } 99 | 100 | sql: { 101 | driver: "mysql" 102 | source: "ivan:pwd@/nss_db" 103 | no_caching: true 104 | max_open_conns: 5 105 | bulk_insert_limit: 1000 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /test/sqlstore.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2018 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package test 15 | 16 | import ( 17 | "bufio" 18 | "database/sql" 19 | "flag" 20 | "fmt" 21 | "os" 22 | "strings" 23 | ) 24 | 25 | // Driver names. 26 | const ( 27 | DriverMySQL = "mysql" 28 | DriverPostgres = "postgres" 29 | ) 30 | 31 | // CreateSQLDatabase initializes a SQL Database for NATS Streaming testing. 32 | func CreateSQLDatabase(driver, sourceAdmin, source, dbName string) error { 33 | db, err := sql.Open(driver, sourceAdmin) 34 | if err != nil { 35 | return fmt.Errorf("error opening connection to SQL datastore %q: %v", sourceAdmin, err) 36 | } 37 | defer db.Close() 38 | if _, err := db.Exec("DROP DATABASE IF EXISTS " + dbName); err != nil { 39 | return fmt.Errorf("error dropping database: %v", err) 40 | } 41 | switch driver { 42 | case DriverMySQL: 43 | if _, err := db.Exec("CREATE DATABASE IF NOT EXISTS " + dbName); err != nil { 44 | return fmt.Errorf("error creating database: %v", err) 45 | } 46 | if _, err = db.Exec("USE " + dbName); err != nil { 47 | return fmt.Errorf("error using database %q: %v", dbName, err) 48 | } 49 | case DriverPostgres: 50 | if _, err := db.Exec("CREATE DATABASE " + dbName); err != nil { 51 | return fmt.Errorf("error creating database: %v", err) 52 | } 53 | db.Close() 54 | db, err = sql.Open(driver, source) 55 | if err != nil { 56 | return fmt.Errorf("error connecting to database: %v", err) 57 | } 58 | defer db.Close() 59 | default: 60 | panic(fmt.Sprintf("Unsupported driver %v", driver)) 61 | } 62 | sqlCreateDatabase, err := loadCreateDatabaseStmts(driver) 63 | if err != nil { 64 | return err 65 | } 66 | for _, stmt := range sqlCreateDatabase { 67 | if _, err := db.Exec(stmt); err != nil { 68 | return fmt.Errorf("error executing statement (%s): %v", stmt, err) 69 | } 70 | } 71 | return nil 72 | } 73 | 74 | func loadCreateDatabaseStmts(driver string) ([]string, error) { 75 | fileName := "../scripts/" + driver + ".db.sql" 76 | file, err := os.Open(fileName) 77 | if err != nil { 78 | return nil, fmt.Errorf("error opening file: %v", err) 79 | } 80 | defer file.Close() 81 | stmts := []string{} 82 | scanner := bufio.NewScanner(file) 83 | for scanner.Scan() { 84 | line := scanner.Text() 85 | line = strings.TrimLeft(line, " ") 86 | if len(line) == 0 || line[0] == '#' || line[0] == '-' { 87 | continue 88 | } 89 | stmts = append(stmts, line) 90 | } 91 | if err := scanner.Err(); err != nil { 92 | return nil, fmt.Errorf("error scanning file: %v", err) 93 | } 94 | return stmts, nil 95 | } 96 | 97 | // CleanupSQLDatastore empties the tables from the NATS Streaming database. 98 | func CleanupSQLDatastore(t TLogger, driver, source string) { 99 | db, err := sql.Open(driver, source) 100 | if err != nil { 101 | StackFatalf(t, "Error cleaning up SQL datastore: %v", err) 102 | } 103 | defer db.Close() 104 | MustExecuteSQL(t, db, "DELETE FROM StoreLock") 105 | MustExecuteSQL(t, db, "DELETE FROM ServerInfo") 106 | MustExecuteSQL(t, db, "DELETE FROM Clients") 107 | MustExecuteSQL(t, db, "DELETE FROM Channels") 108 | MustExecuteSQL(t, db, "DELETE FROM Messages") 109 | MustExecuteSQL(t, db, "DELETE FROM Subscriptions") 110 | MustExecuteSQL(t, db, "DELETE FROM SubsPending") 111 | } 112 | 113 | // DeleteSQLDatabase drops the given database. 114 | func DeleteSQLDatabase(driver, sourceAdmin, dbName string) error { 115 | db, err := sql.Open(driver, sourceAdmin) 116 | if err != nil { 117 | return err 118 | } 119 | defer db.Close() 120 | _, err = db.Exec("DROP DATABASE " + dbName) 121 | return err 122 | } 123 | 124 | // MustExecuteSQL excutes the given SQL query and is not expecting an error. 125 | // If it does, it calls t.Fatalf(). 126 | func MustExecuteSQL(t TLogger, db *sql.DB, query string, args ...interface{}) sql.Result { 127 | r, err := db.Exec(query, args...) 128 | if err != nil { 129 | StackFatalf(t, "Error executing query %q: %v", query, err) 130 | } 131 | return r 132 | } 133 | 134 | // AddSQLFlags adds some SQL options to the given flag set. 135 | func AddSQLFlags(fs *flag.FlagSet, driver, source, sourceAdmin, dbName *string) { 136 | flag.StringVar(driver, "sql_driver", *driver, "SQL Driver to use") 137 | flag.StringVar(source, "sql_source", "", "SQL data source") 138 | flag.StringVar(sourceAdmin, "sql_source_admin", "", "SQL data source to create the database") 139 | flag.StringVar(dbName, "sql_db_name", *dbName, "SQL database name") 140 | } 141 | 142 | // ProcessSQLFlags allows to just specify the driver on the command line and 143 | // use corresponding driver defaults, while still allowing full customization 144 | // of each param. 145 | func ProcessSQLFlags(fs *flag.FlagSet, defaults map[string][]string) error { 146 | driver := fs.Lookup("sql_driver").Value.String() 147 | switch driver { 148 | case DriverMySQL, DriverPostgres: 149 | default: 150 | return fmt.Errorf("unsupported SQL driver %q", driver) 151 | } 152 | defaultsSources := defaults[driver] 153 | source := fs.Lookup("sql_source") 154 | if source.Value.String() == "" { 155 | source.Value.Set(defaultsSources[0]) 156 | } 157 | sourceAdmin := fs.Lookup("sql_source_admin") 158 | if sourceAdmin.Value.String() == "" { 159 | sourceAdmin.Value.Set(defaultsSources[1]) 160 | } 161 | return nil 162 | } 163 | -------------------------------------------------------------------------------- /test/test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2018 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package test 15 | 16 | import ( 17 | "fmt" 18 | "runtime" 19 | "strings" 20 | ) 21 | 22 | // TLogger is used both in testing.B and testing.T so need to use a common interface 23 | type TLogger interface { 24 | Fatalf(format string, args ...interface{}) 25 | Errorf(format string, args ...interface{}) 26 | } 27 | 28 | // StackFatalf produces a stack trace and passes it to t.Fatalf() 29 | func StackFatalf(t TLogger, f string, args ...interface{}) { 30 | lines := make([]string, 0, 32) 31 | msg := fmt.Sprintf(f, args...) 32 | lines = append(lines, msg) 33 | 34 | // Generate the Stack of callers: 35 | for i := 1; true; i++ { 36 | _, file, line, ok := runtime.Caller(i) 37 | if !ok { 38 | break 39 | } 40 | msg := fmt.Sprintf("%d - %s:%d", i, file, line) 41 | lines = append(lines, msg) 42 | } 43 | 44 | t.Fatalf("%s", strings.Join(lines, "\n")) 45 | // For staticcheck SA0511... 46 | panic("unreachable code") 47 | } 48 | -------------------------------------------------------------------------------- /util/channels.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package util 15 | 16 | import ( 17 | "errors" 18 | "fmt" 19 | 20 | "github.com/nats-io/nats-streaming-server/spb" 21 | "github.com/nats-io/nats.go" 22 | ) 23 | 24 | // Number of bytes used to encode a channel name 25 | const encodedChannelLen = 2 26 | 27 | // SendChannelsList sends the list of channels to the given subject, possibly 28 | // splitting the list in several requests if it cannot fit in a single message. 29 | func SendChannelsList(channels []string, sendInbox, replyInbox string, nc *nats.Conn, serverID string) error { 30 | // Since the NATS message payload is limited, we need to repeat 31 | // requests if all channels can't fit in a request. 32 | maxPayload := int(nc.MaxPayload()) 33 | // Reuse this request object to send the (possibly many) protocol message(s). 34 | header := &spb.CtrlMsg{ 35 | ServerID: serverID, 36 | MsgType: spb.CtrlMsg_Partitioning, 37 | } 38 | // The Data field (a byte array) will require 1+len(array)+(encoded size of array). 39 | // To be conservative, let's just use a 8 bytes integer 40 | headerSize := header.Size() + 1 + 8 41 | var ( 42 | bytes []byte // Reused buffer in which the request is to marshal info 43 | n int // Size of the serialized request in the above buffer 44 | count int // Number of channels added to the request 45 | ) 46 | for start := 0; start != len(channels); start += count { 47 | bytes, n, count = encodeChannelsRequest(header, channels, bytes, headerSize, maxPayload, start) 48 | if count == 0 { 49 | return errors.New("message payload too small to send channels list") 50 | } 51 | if err := nc.PublishRequest(sendInbox, replyInbox, bytes[:n]); err != nil { 52 | return err 53 | } 54 | } 55 | return nc.Flush() 56 | } 57 | 58 | // DecodeChannels decodes from the given byte array the list of channel names 59 | // and return them as an array of strings. 60 | func DecodeChannels(data []byte) ([]string, error) { 61 | channels := []string{} 62 | pos := 0 63 | for pos < len(data) { 64 | if pos+2 > len(data) { 65 | return nil, fmt.Errorf("unable to decode size, pos=%v len=%v", pos, len(data)) 66 | } 67 | cl := int(ByteOrder.Uint16(data[pos:])) 68 | pos += encodedChannelLen 69 | end := pos + cl 70 | if end > len(data) { 71 | return nil, fmt.Errorf("unable to decode channel, pos=%v len=%v max=%v (string=%v)", 72 | pos, cl, len(data), string(data[pos:])) 73 | } 74 | c := string(data[pos:end]) 75 | channels = append(channels, c) 76 | pos = end 77 | } 78 | return channels, nil 79 | } 80 | 81 | // Adds as much channels as possible (based on the NATS max message payload) and 82 | // returns a serialized request. The buffer `reqBytes` is passed (and returned) so 83 | // that it can be reused if more than one request is needed. This call will 84 | // expand the size as needed. The number of bytes used in this buffer is returned 85 | // along with the number of encoded channels. 86 | func encodeChannelsRequest(request *spb.CtrlMsg, channels []string, reqBytes []byte, 87 | headerSize, maxPayload, start int) ([]byte, int, int) { 88 | 89 | // Each string will be encoded in the form: 90 | // - length (2 bytes) 91 | // - string as a byte array. 92 | var _encodedSize = [encodedChannelLen]byte{} 93 | encodedSize := _encodedSize[:] 94 | // We are going to encode the channels in this buffer 95 | chanBuf := make([]byte, 0, maxPayload) 96 | var ( 97 | count int // Number of encoded channels 98 | estimatedSize = headerSize // This is not an overestimation of the total size 99 | numBytes int // This is what is returned by MarshalTo 100 | ) 101 | for i := start; i < len(channels); i++ { 102 | c := []byte(channels[i]) 103 | cl := len(c) 104 | needed := encodedChannelLen + cl 105 | // Check if adding this channel to current buffer makes us go over 106 | if estimatedSize+needed > maxPayload { 107 | // Special case if we cannot even encode 1 channel 108 | if count == 0 { 109 | return reqBytes, 0, 0 110 | } 111 | break 112 | } 113 | // Encoding the channel here. First the size, then the channel name as byte array. 114 | ByteOrder.PutUint16(encodedSize, uint16(cl)) 115 | chanBuf = append(chanBuf, encodedSize...) 116 | chanBuf = append(chanBuf, c...) 117 | count++ 118 | estimatedSize += needed 119 | } 120 | if count > 0 { 121 | request.Data = chanBuf 122 | reqBytes = EnsureBufBigEnough(reqBytes, estimatedSize) 123 | numBytes, _ = request.MarshalTo(reqBytes) 124 | if numBytes > maxPayload { 125 | panic(fmt.Errorf("request size is %v (max payload is %v)", numBytes, maxPayload)) 126 | } 127 | } 128 | return reqBytes, numBytes, count 129 | } 130 | -------------------------------------------------------------------------------- /util/lockfile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2019 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package util 15 | 16 | import ( 17 | "flag" 18 | "fmt" 19 | "os" 20 | "os/exec" 21 | "strings" 22 | "sync" 23 | "testing" 24 | "time" 25 | ) 26 | 27 | var lockFileName = flag.String("lockFile", "", "") 28 | 29 | func TestLockFile(t *testing.T) { 30 | // To test locking, we need to spawn a new process to check that 31 | // the file cannot be locked by another process. 32 | // We have the parent lock the file and try to have a child process 33 | // lock it, then we reverse the test: this is just for code coverage 34 | // that would otherwise not show the code coverage for when the 35 | // file is already locked. 36 | 37 | fname := *lockFileName 38 | parentProcess := fname == "" 39 | if fname == "" { 40 | fname = "test.lck" 41 | defer os.Remove(fname) 42 | } 43 | 44 | // First test with invalid file 45 | if parentProcess { 46 | f, err := CreateLockFile("dummy/test.lck") 47 | if err == nil { 48 | f.Close() 49 | t.Fatal("Expected to fail for invalid file name") 50 | } 51 | } 52 | 53 | f, err := CreateLockFile(fname) 54 | if err != nil { 55 | t.Fatalf("Error creating lock file: %v", err) 56 | } 57 | defer f.Close() 58 | if !parentProcess { 59 | // As a child process that got the lock, create a file 60 | // that the parent can check the existence of to proceed. 61 | wf, err := os.Create("test_child.txt") 62 | if err != nil { 63 | t.Fatalf("Child process unable to create file: %v", err) 64 | } 65 | wf.Close() 66 | defer os.Remove(wf.Name()) 67 | } 68 | 69 | // If we are the parent process, try to spawn a new process 70 | // that will try to grab the lock for same file 71 | if parentProcess { 72 | out, err := exec.Command(os.Args[0], "-lockFile", fname, 73 | "-test.v", "-test.run=TestLockFile$").CombinedOutput() 74 | if err == nil { 75 | t.Fatal("CreateLockFile should have failed while other process holds lock") 76 | } 77 | if !strings.Contains(string(out), ErrUnableToLockNow.Error()) { 78 | t.Fatalf("Error should contain: %q, got %q", ErrUnableToLockNow.Error(), string(out)) 79 | } 80 | } else { 81 | // If the child process, wait a bit while parent process 82 | // tries to get the lock. 83 | for { 84 | if _, err := os.Stat("test_parent.txt"); err != nil { 85 | time.Sleep(100 * time.Millisecond) 86 | continue 87 | } 88 | break 89 | } 90 | } 91 | // Release the lock 92 | if err := f.Close(); err != nil { 93 | t.Fatalf("Unexpected error on close: %v", err) 94 | } 95 | // Check state 96 | if !f.IsClosed() { 97 | t.Fatal("File should be closed") 98 | } 99 | if parentProcess { 100 | // Try again with different process, it should work now. 101 | wg := sync.WaitGroup{} 102 | wg.Add(1) 103 | errCh := make(chan error, 1) 104 | go func() { 105 | defer wg.Done() 106 | out, err := exec.Command(os.Args[0], "-lockFile", fname, 107 | "-test.v", "-test.run=TestLockFile$").CombinedOutput() 108 | if err != nil { 109 | errCh <- fmt.Errorf("Other process should have been able to get the lock, got %v - %v", 110 | err, out) 111 | } 112 | }() 113 | // Give a chance for the child process to run 114 | for { 115 | if _, err := os.Stat("test_child.txt"); err != nil { 116 | time.Sleep(100 * time.Millisecond) 117 | continue 118 | } 119 | break 120 | } 121 | // The parent process should now be the one that fails 122 | f, err := CreateLockFile(fname) 123 | if err == nil { 124 | f.Close() 125 | t.Fatal("Expected CreateLockFile to fail, it did not") 126 | } 127 | // Write a file to indicate to the child process that we are done 128 | wf, err := os.Create("test_parent.txt") 129 | if err != nil { 130 | t.Fatalf("Parent process unable to create file: %v", err) 131 | } 132 | wf.Close() 133 | defer os.Remove(wf.Name()) 134 | wg.Wait() 135 | select { 136 | case e := <-errCh: 137 | t.Fatal(e.Error()) 138 | default: 139 | } 140 | } 141 | } 142 | 143 | func TestLockFileFatalErrors(t *testing.T) { 144 | // Specifying a wrong path should return an error 145 | lf, err := CreateLockFile("dummy/lock.lck") 146 | if lf != nil || err == nil { 147 | if lf != nil { 148 | lf.Close() 149 | } 150 | t.Fatalf("Expected no file and error, got %v, %v", lf, err) 151 | } 152 | 153 | // Try with permission error. First, create a file 154 | fileName := "test.lck" 155 | defer os.Remove(fileName) 156 | defer os.Chmod(fileName, 0666) 157 | file, err := os.Create(fileName) 158 | if err != nil { 159 | t.Fatalf("Unable to create file: %v", err) 160 | } 161 | file.Close() 162 | os.Chmod(fileName, 0400) 163 | lf, err = CreateLockFile(fileName) 164 | if lf != nil || err == nil { 165 | if lf != nil { 166 | lf.Close() 167 | } 168 | t.Fatalf("Expected no file and error, got %v, %v", lf, err) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /util/lockfile_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2022 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //go:build !windows 15 | // +build !windows 16 | 17 | package util 18 | 19 | import ( 20 | "io" 21 | "os" 22 | "sync" 23 | "syscall" 24 | ) 25 | 26 | type lockFile struct { 27 | sync.Mutex 28 | f *os.File 29 | } 30 | 31 | // CreateLockFile attempt to lock the given file, creating it 32 | // if necessary. On success, the file is returned, otherwise 33 | // an error is returned. 34 | // The file returned should be closed to release the lock 35 | // quicker than if left to the operating system. 36 | func CreateLockFile(file string) (LockFile, error) { 37 | f, err := os.Create(file) 38 | if err != nil { 39 | // Consider those fatal, others may be considered transient 40 | // (for instance FD limit reached, etc...) 41 | if os.IsNotExist(err) || os.IsPermission(err) { 42 | return nil, err 43 | } 44 | return nil, ErrUnableToLockNow 45 | } 46 | spec := &syscall.Flock_t{ 47 | Type: syscall.F_WRLCK, 48 | Whence: int16(io.SeekStart), 49 | Start: 0, 50 | Len: 0, // 0 means to lock the entire file. 51 | } 52 | if err := syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, spec); err != nil { 53 | // Try to gather all errors that we deem transient and return 54 | // ErrUnableToLockNow in this case to indicate the caller that 55 | // the lock could not be acquired at this time but it could 56 | // try later. 57 | // Basing this from possible ERRORS from this page: 58 | // http://pubs.opengroup.org/onlinepubs/009695399/functions/fcntl.html 59 | if err == syscall.EAGAIN || err == syscall.EACCES || 60 | err == syscall.EINTR || err == syscall.ENOLCK { 61 | err = ErrUnableToLockNow 62 | } 63 | // TODO: If error is not ErrUnableToLockNow, it may mean that 64 | // the call is not supported on that platform, etc... 65 | // We should have another level of verification, for instance 66 | // check content of the lockfile is not being updated by the 67 | // owner of the file, etc... 68 | f.Close() 69 | return nil, err 70 | } 71 | return &lockFile{f: f}, nil 72 | } 73 | 74 | // Close implements the LockFile interface 75 | func (lf *lockFile) Close() error { 76 | lf.Lock() 77 | defer lf.Unlock() 78 | if lf.f == nil { 79 | return nil 80 | } 81 | spec := &syscall.Flock_t{ 82 | Type: syscall.F_UNLCK, 83 | Whence: int16(io.SeekStart), 84 | Start: 0, 85 | Len: 0, // 0 means to lock the entire file. 86 | } 87 | err := syscall.FcntlFlock(lf.f.Fd(), syscall.F_SETLK, spec) 88 | err = CloseFile(err, lf.f) 89 | lf.f = nil 90 | return err 91 | } 92 | 93 | // IsClosed implements the LockFile interface 94 | func (lf *lockFile) IsClosed() bool { 95 | lf.Lock() 96 | defer lf.Unlock() 97 | return lf.f == nil 98 | } 99 | -------------------------------------------------------------------------------- /util/lockfile_win.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2022 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //go:build windows 15 | // +build windows 16 | 17 | package util 18 | 19 | import ( 20 | "strings" 21 | "sync" 22 | "syscall" 23 | ) 24 | 25 | type lockFile struct { 26 | sync.Mutex 27 | f syscall.Handle 28 | } 29 | 30 | // CreateLockFile attempt to lock the given file, creating it 31 | // if necessary. On success, the file is returned, otherwise 32 | // an error is returned. 33 | // The file returned should be closed to release the lock 34 | // quicker than if left to the operating system. 35 | func CreateLockFile(file string) (LockFile, error) { 36 | fname, err := syscall.UTF16PtrFromString(file) 37 | if err != nil { 38 | return nil, err 39 | } 40 | f, err := syscall.CreateFile(fname, 41 | syscall.GENERIC_READ|syscall.GENERIC_WRITE, 42 | 0, // dwShareMode: 0 means "Prevents other processes from opening a file or device if they request delete, read, or write access." 43 | nil, 44 | syscall.CREATE_ALWAYS, 45 | syscall.FILE_ATTRIBUTE_NORMAL, 46 | 0, 47 | ) 48 | if err != nil { 49 | // TODO: There HAS to be a better way, but I can't seem to 50 | // find how to get Windows error codes (also syscall.GetLastError() 51 | // returns nil here). 52 | if strings.Contains(err.Error(), "used by another process") { 53 | err = ErrUnableToLockNow 54 | } 55 | syscall.CloseHandle(f) 56 | return nil, err 57 | } 58 | return &lockFile{f: f}, nil 59 | } 60 | 61 | // Close implements the LockFile interface 62 | func (lf *lockFile) Close() error { 63 | lf.Lock() 64 | defer lf.Unlock() 65 | if lf.f == syscall.InvalidHandle { 66 | return nil 67 | } 68 | err := syscall.CloseHandle(lf.f) 69 | lf.f = syscall.InvalidHandle 70 | return err 71 | } 72 | 73 | // IsClosed implements the LockFile interface 74 | func (lf *lockFile) IsClosed() bool { 75 | lf.Lock() 76 | defer lf.Unlock() 77 | return lf.f == syscall.InvalidHandle 78 | } 79 | -------------------------------------------------------------------------------- /util/no_race.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //go:build !race 15 | // +build !race 16 | 17 | package util 18 | 19 | // RaceEnabled indicates that program/tests are running with race detection 20 | // enabled or not. Some tests may chose to skip execution when race 21 | // detection is on. 22 | const RaceEnabled = false 23 | -------------------------------------------------------------------------------- /util/race.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | //go:build race 15 | // +build race 16 | 17 | package util 18 | 19 | // RaceEnabled indicates that program/tests are running with race detection 20 | // enabled or not. Some tests may chose to skip execution when race 21 | // detection is on. 22 | const RaceEnabled = true 23 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2018 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package util 15 | 16 | import ( 17 | "encoding/binary" 18 | "errors" 19 | "fmt" 20 | "io" 21 | "math" 22 | "time" 23 | ) 24 | 25 | // ErrUnableToLockNow is used to indicate that a lock cannot be 26 | // immediately acquired. 27 | var ErrUnableToLockNow = errors.New("unable to acquire the lock at the moment") 28 | 29 | // LockFile is an interface for lock files utility. 30 | type LockFile interface { 31 | io.Closer 32 | IsClosed() bool 33 | } 34 | 35 | // ByteOrder specifies how to convert byte sequences into 16-, 32-, or 64-bit 36 | // unsigned integers. 37 | var ByteOrder binary.ByteOrder 38 | 39 | func init() { 40 | ByteOrder = binary.LittleEndian 41 | } 42 | 43 | // BackoffTimeCheck allows to execute some code, but not too often. 44 | type BackoffTimeCheck struct { 45 | nextTime time.Time 46 | frequency time.Duration 47 | minFrequency time.Duration 48 | maxFrequency time.Duration 49 | factor int 50 | } 51 | 52 | // NewBackoffTimeCheck creates an instance of BackoffTimeCheck. 53 | // The `minFrequency` indicates how frequently BackoffTimeCheck.Ok() can return true. 54 | // When Ok() returns true, the allowed frequency is multiplied by `factor`. The 55 | // resulting frequency is capped by `maxFrequency`. 56 | func NewBackoffTimeCheck(minFrequency time.Duration, factor int, maxFrequency time.Duration) (*BackoffTimeCheck, error) { 57 | if minFrequency <= 0 || factor < 1 || maxFrequency < minFrequency { 58 | return nil, fmt.Errorf("minFrequency must be positive, factor at least 1 and maxFrequency at least equal to minFrequency, got %v - %v - %v", 59 | minFrequency, factor, maxFrequency) 60 | } 61 | return &BackoffTimeCheck{ 62 | frequency: minFrequency, 63 | minFrequency: minFrequency, 64 | maxFrequency: maxFrequency, 65 | factor: factor, 66 | }, nil 67 | } 68 | 69 | // Ok returns true for the first time it is invoked after creation of the object 70 | // or call to Reset(), or after an amount of time (based on the last success 71 | // and the allowed frequency) has elapsed. 72 | // When at the maximum frequency, if this call is made after a delay at least 73 | // equal to 3x the max frequency (or in other words, 2x after what was the target 74 | // for the next print), then the object is auto-reset. 75 | func (bp *BackoffTimeCheck) Ok() bool { 76 | if bp.nextTime.IsZero() { 77 | bp.nextTime = time.Now().Add(bp.minFrequency) 78 | return true 79 | } 80 | now := time.Now() 81 | if now.Before(bp.nextTime) { 82 | return false 83 | } 84 | // If we are already at the max frequency and this call 85 | // is made after 2x the max frequency, then auto-reset. 86 | if bp.frequency == bp.maxFrequency && 87 | now.Sub(bp.nextTime) >= 2*bp.maxFrequency { 88 | bp.Reset() 89 | return true 90 | } 91 | if bp.frequency < bp.maxFrequency { 92 | bp.frequency *= time.Duration(bp.factor) 93 | if bp.frequency > bp.maxFrequency { 94 | bp.frequency = bp.maxFrequency 95 | } 96 | } 97 | bp.nextTime = now.Add(bp.frequency) 98 | return true 99 | } 100 | 101 | // Reset the state so that next call to BackoffPrint.Ok() will return true. 102 | func (bp *BackoffTimeCheck) Reset() { 103 | bp.nextTime = time.Time{} 104 | bp.frequency = bp.minFrequency 105 | } 106 | 107 | // EnsureBufBigEnough checks that given buffer is big enough to hold 'needed' 108 | // bytes, otherwise returns a buffer of a size of at least 'needed' bytes. 109 | func EnsureBufBigEnough(buf []byte, needed int) []byte { 110 | if buf == nil { 111 | return make([]byte, needed) 112 | } else if needed > len(buf) { 113 | return make([]byte, int(float32(needed)*1.1)) 114 | } 115 | return buf 116 | } 117 | 118 | // WriteInt writes an int (4 bytes) to the given writer using ByteOrder. 119 | func WriteInt(w io.Writer, v int) error { 120 | var b [4]byte 121 | 122 | bs := b[:4] 123 | 124 | ByteOrder.PutUint32(bs, uint32(v)) 125 | _, err := w.Write(bs) 126 | return err 127 | } 128 | 129 | // ReadInt reads an int (4 bytes) from the reader using ByteOrder. 130 | func ReadInt(r io.Reader) (int, error) { 131 | var b [4]byte 132 | 133 | bs := b[:4] 134 | 135 | _, err := io.ReadFull(r, bs) 136 | if err != nil { 137 | return 0, err 138 | } 139 | return int(ByteOrder.Uint32(bs)), nil 140 | } 141 | 142 | // CloseFile closes the given file and report the possible error only 143 | // if the given error `err` is not already set. 144 | func CloseFile(err error, f io.Closer) error { 145 | if lerr := f.Close(); lerr != nil && err == nil { 146 | err = lerr 147 | } 148 | return err 149 | } 150 | 151 | // IsChannelNameValid returns false if any of these conditions for 152 | // the channel name apply: 153 | // - is empty 154 | // - contains the `/` character 155 | // - token separator `.` is first or last 156 | // - there are two consecutives token separators `.` 157 | // if wildcardsAllowed is false: 158 | // - contains wildcards `*` or `>` 159 | // if wildcardsAllowed is true: 160 | // - '*' or '>' are not a token in their own 161 | // - `>` is not the last token 162 | func IsChannelNameValid(channel string, wildcardsAllowed bool) bool { 163 | if channel == "" || channel[0] == btsep { 164 | return false 165 | } 166 | for i := 0; i < len(channel); i++ { 167 | c := channel[i] 168 | if c == '/' { 169 | return false 170 | } 171 | if (c == btsep) && (i == len(channel)-1 || channel[i+1] == btsep) { 172 | return false 173 | } 174 | if !wildcardsAllowed { 175 | if c == pwc || c == fwc { 176 | return false 177 | } 178 | } else if c == pwc || c == fwc { 179 | if i > 0 && channel[i-1] != btsep { 180 | return false 181 | } 182 | if c == fwc && i != len(channel)-1 { 183 | return false 184 | } 185 | if i < len(channel)-1 && channel[i+1] != btsep { 186 | return false 187 | } 188 | } 189 | } 190 | return true 191 | } 192 | 193 | // IsChannelNameLiteral returns true if the channel name is a literal (that is, 194 | // it does not contain any wildcard). 195 | // The channel name is assumed to be valid. 196 | func IsChannelNameLiteral(channel string) bool { 197 | for i := 0; i < len(channel); i++ { 198 | if channel[i] == pwc || channel[i] == fwc { 199 | return false 200 | } 201 | } 202 | return true 203 | } 204 | 205 | // FriendlyBytes returns a string with the given bytes int64 206 | // represented as a size, such as 1KB, 10MB, etc... 207 | func FriendlyBytes(bytes int64) string { 208 | fbytes := float64(bytes) 209 | base := 1024 210 | pre := []string{"K", "M", "G", "T", "P", "E"} 211 | if fbytes < float64(base) { 212 | return fmt.Sprintf("%v B", fbytes) 213 | } 214 | exp := int(math.Log(fbytes) / math.Log(float64(base))) 215 | index := exp - 1 216 | return fmt.Sprintf("%.2f %sB", fbytes/math.Pow(float64(base), float64(exp)), pre[index]) 217 | } 218 | -------------------------------------------------------------------------------- /util/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2018 The NATS Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package util 15 | 16 | import ( 17 | "flag" 18 | "fmt" 19 | "os" 20 | "runtime" 21 | "strings" 22 | "testing" 23 | "time" 24 | ) 25 | 26 | func TestMain(m *testing.M) { 27 | // This one is added here so that if we want to disable sql for stores tests 28 | // we can use the same param for all packages as in "go test -v ./... -sql=false" 29 | flag.Bool("sql", false, "Not used for util tests") 30 | os.Exit(m.Run()) 31 | } 32 | 33 | func stackFatalf(t *testing.T, f string, args ...interface{}) { 34 | lines := make([]string, 0, 32) 35 | msg := fmt.Sprintf(f, args...) 36 | lines = append(lines, msg) 37 | 38 | // Generate the Stack of callers: 39 | for i := 1; true; i++ { 40 | _, file, line, ok := runtime.Caller(i) 41 | if !ok { 42 | break 43 | } 44 | msg := fmt.Sprintf("%d - %s:%d", i, file, line) 45 | lines = append(lines, msg) 46 | } 47 | 48 | t.Fatalf("%s", strings.Join(lines, "\n")) 49 | // For staticcheck SA0511... 50 | panic("unreachable code") 51 | } 52 | 53 | func TestEnsureBufBigEnough(t *testing.T) { 54 | buf := make([]byte, 3) 55 | newBuf := EnsureBufBigEnough(buf, 2) 56 | if len(newBuf) != len(buf) { 57 | t.Fatal("EnsureBufBigEnough should not have allocated a new buffer") 58 | } 59 | newBuf = EnsureBufBigEnough(buf, 10) 60 | if len(newBuf) <= 10 { 61 | t.Fatalf("Buffer should be at least 10, it is: %v", len(newBuf)) 62 | } 63 | newBuf = EnsureBufBigEnough(nil, 5) 64 | if len(newBuf) != 5 { 65 | t.Fatalf("Buffer should be exactly 5, it is: %v", len(newBuf)) 66 | } 67 | } 68 | 69 | func TestWriteInt(t *testing.T) { 70 | fileName := "test.dat" 71 | file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0666) 72 | if err != nil { 73 | t.Fatalf("Unexpected error: %v", err) 74 | } 75 | defer os.Remove(fileName) 76 | defer file.Close() 77 | if err := WriteInt(file, 123); err != nil { 78 | t.Fatalf("Unexpected error: %v", err) 79 | } 80 | } 81 | 82 | func TestReadInt(t *testing.T) { 83 | fileName := "test.dat" 84 | file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0666) 85 | if err != nil { 86 | t.Fatalf("Unexpected error: %v", err) 87 | } 88 | defer os.Remove(fileName) 89 | defer file.Close() 90 | 91 | if _, err := ReadInt(file); err == nil { 92 | t.Fatal("Expected an error") 93 | } 94 | if err := WriteInt(file, 123); err != nil { 95 | t.Fatalf("Unexpected error: %v", err) 96 | } 97 | if _, err := file.Seek(0, 0); err != nil { 98 | t.Fatalf("Unexpected error: %v", err) 99 | } 100 | if v, err := ReadInt(file); err != nil || v != 123 { 101 | t.Fatalf("Expected to read 123, got: %v (err=%v)", v, err) 102 | } 103 | } 104 | 105 | func TestCloseFile(t *testing.T) { 106 | fileName := "test.dat" 107 | file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0666) 108 | if err != nil { 109 | t.Fatalf("Unexpected error: %v", err) 110 | } 111 | defer os.Remove(fileName) 112 | 113 | err = nil 114 | cferr := CloseFile(err, file) 115 | if cferr != nil { 116 | t.Fatalf("Unexpected error: %v", cferr) 117 | } 118 | 119 | err = fmt.Errorf("Previous error") 120 | cferr = CloseFile(err, file) 121 | if cferr != err { 122 | t.Fatalf("Expected original error to be untouched") 123 | } 124 | 125 | err = nil 126 | cferr = CloseFile(err, file) 127 | if cferr == err { 128 | t.Fatalf("Expected returned error to be different") 129 | } 130 | } 131 | 132 | func TestBackoffTimeCheck(t *testing.T) { 133 | // Check invalid values 134 | if btc, err := NewBackoffTimeCheck(-1, 1, time.Second); btc != nil || err == nil { 135 | t.Fatalf("NewBackoffTimeCheck returned: %v, %v", btc, err) 136 | } 137 | if btc, err := NewBackoffTimeCheck(0, 1, time.Second); btc != nil || err == nil { 138 | t.Fatalf("NewBackoffTimeCheck returned: %v, %v", btc, err) 139 | } 140 | if btc, err := NewBackoffTimeCheck(time.Second, 0, time.Second); btc != nil || err == nil { 141 | t.Fatalf("NewBackoffTimeCheck returned: %v, %v", btc, err) 142 | } 143 | if btc, err := NewBackoffTimeCheck(time.Second, -1, time.Second); btc != nil || err == nil { 144 | t.Fatalf("NewBackoffTimeCheck returned: %v, %v", btc, err) 145 | } 146 | if btc, err := NewBackoffTimeCheck(time.Second, 1, -1); btc != nil || err == nil { 147 | t.Fatalf("NewBackoffTimeCheck returned: %v, %v", btc, err) 148 | } 149 | if btc, err := NewBackoffTimeCheck(time.Second, 1, 0); btc != nil || err == nil { 150 | t.Fatalf("NewBackoffTimeCheck returned: %v, %v", btc, err) 151 | } 152 | if btc, err := NewBackoffTimeCheck(time.Second, 1, time.Millisecond); btc != nil || err == nil { 153 | t.Fatalf("NewBackoffTimeCheck returned: %v, %v", btc, err) 154 | } 155 | 156 | // Create a time check for printing. 157 | print, _ := NewBackoffTimeCheck(20*time.Millisecond, 2, 100*time.Millisecond) 158 | start := time.Now() 159 | if !print.Ok() { 160 | t.Fatal("Should have returned true") 161 | } 162 | if print.Ok() { 163 | if elapsed := time.Since(start); elapsed < 20*time.Millisecond { 164 | t.Fatalf("Should have returned false, only %v elapsed", elapsed) 165 | } 166 | } 167 | start = time.Now() 168 | time.Sleep(30 * time.Millisecond) 169 | if !print.Ok() { 170 | if elapsed := time.Since(start); elapsed > 20*time.Millisecond { 171 | t.Fatalf("Should have returned true, %v elapsed", elapsed) 172 | } 173 | } 174 | // Now Reset and call, it should succeed 175 | print.Reset() 176 | if !print.Ok() { 177 | t.Fatal("Should have returned true") 178 | } 179 | // Repeat calls until frequency is increased to the max 180 | freqs := make([]time.Duration, 0) 181 | last := time.Now() 182 | timeout := time.Now().Add(400 * time.Millisecond) 183 | for time.Now().Before(timeout) { 184 | if print.Ok() { 185 | freqs = append(freqs, time.Since(last)) 186 | last = time.Now() 187 | } 188 | } 189 | // from the start, we should have printed, after the start at these times: 190 | // 0:00:20ms, 0:00:40ms, 0:00:80ms, 0:00:100ms, 0:00:200ms 191 | // but we max at 100, so expected values are: 192 | expected := []int64{20, 40, 80, 100, 100} 193 | if len(freqs) != 5 { 194 | t.Fatalf("Expected ok 5 times, got %v", len(freqs)) 195 | } 196 | for i, f := range freqs { 197 | dur := time.Duration(expected[i] * int64(time.Millisecond)) 198 | if f < dur-15*time.Millisecond || f > dur+15*time.Millisecond { 199 | t.Fatalf("Expected frequency to be +/- %v, got %v", dur, f) 200 | } 201 | } 202 | // Now that we know that we have reached the max frequency, 203 | // we are going to test the auto-reset. We need to wait that 204 | // 2x the max frequency pass *after* the allowed next print, 205 | // which at this point is 100ms ahead of us. So we need to 206 | // sleep for at least 300ms. Sleep a bit more. 207 | time.Sleep(500 * time.Millisecond) 208 | // At this point, it is as if we were calling for the first time: 209 | if !print.Ok() { 210 | t.Fatal("Should have returned true") 211 | } 212 | // Check internals 213 | if !print.nextTime.IsZero() { 214 | t.Fatal("No auto-reset done") 215 | } 216 | } 217 | 218 | func TestIsChannelNameValid(t *testing.T) { 219 | channel := "" 220 | for i := 0; i < 100; i++ { 221 | channel += "foo." 222 | } 223 | channel += "foo" 224 | if !IsChannelNameValid(channel, false) { 225 | t.Fatalf("Channel %q should be valid", channel) 226 | } 227 | channels := []string{ 228 | "foo.bar*", 229 | "foo.bar>", 230 | "foo.bar.*", 231 | "foo.bar.>", 232 | "foo*.bar", 233 | "foo>.bar", 234 | "foo..bar", 235 | ".foo.bar", 236 | "foo.bar.", 237 | "..", 238 | ".", 239 | "foo/bar", 240 | } 241 | for _, s := range channels { 242 | if IsChannelNameValid(s, false) { 243 | t.Fatalf("Channel %q expected to be invalid", s) 244 | } 245 | } 246 | channels = []string{ 247 | "foo.bar*", 248 | "foo.bar>", 249 | "foo*.bar", 250 | "foo>.bar", 251 | "foo.*bar", 252 | "foo.>bar", 253 | "foo.>.bar", 254 | ">.", 255 | ">.>", 256 | "foo..bar", 257 | ".foo.bar", 258 | "foo.bar.", 259 | "..", 260 | ".", 261 | } 262 | for _, s := range channels { 263 | if IsChannelNameValid(s, true) { 264 | t.Fatalf("Channel %q expected to be invalid", s) 265 | } 266 | } 267 | 268 | // Test valid wildcard channels 269 | channels = []string{ 270 | "foo.*", 271 | "foo.*.*", 272 | "foo.>", 273 | "foo.*.>", 274 | "*", 275 | ">", 276 | "*.bar.*", 277 | "*.bar.>", 278 | "*.>", 279 | } 280 | for _, s := range channels { 281 | if !IsChannelNameValid(s, true) { 282 | t.Fatalf("Channel %q expected to be valid", s) 283 | } 284 | } 285 | } 286 | 287 | func TestIsChannelNameLiteral(t *testing.T) { 288 | channels := []string{"foo.*", "foo.>", "foo.*.bar", "foo.bar.*"} 289 | for _, s := range channels { 290 | if IsChannelNameLiteral(s) { 291 | t.Fatalf("IsChannelNameLiteral for %q should have returned false", s) 292 | } 293 | } 294 | channels = []string{"foo.bar", "foo.baz", "foo.baz.bar", "foo.bar.baz"} 295 | for _, s := range channels { 296 | if !IsChannelNameLiteral(s) { 297 | t.Fatalf("IsChannelNameLiteral for %q should have returned true", s) 298 | } 299 | } 300 | } 301 | 302 | func TestFriendlyBytes(t *testing.T) { 303 | check := func(val int64, expectedSuffix string) { 304 | res := FriendlyBytes(val) 305 | if !strings.HasSuffix(res, expectedSuffix) { 306 | t.Fatalf("For %v, expected suffix to be %v, got %v", val, expectedSuffix, res) 307 | } 308 | } 309 | check(1000, " B") 310 | check(2000, " KB") 311 | check(10<<20, " MB") 312 | check(10<<30, " GB") 313 | check(10<<40, " TB") 314 | check(10<<50, " PB") 315 | check(0xFFFFFFFFFFFFFFF, " EB") 316 | } 317 | --------------------------------------------------------------------------------