From 1462ac6d3291a12566036124d58c0c56907e4028 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Mon, 29 Jan 2024 22:57:35 +0200 Subject: [PATCH 01/32] new storage driver for nats --- .github/dependabot.yml | 8 +- .github/release-drafter-nats.yml | 50 +++ .github/workflows/benchmark.yml | 7 + .github/workflows/release-drafter-nats.yml | 19 ++ .github/workflows/test-nats.yml | 29 ++ .gitignore | 3 + nats/README.md | 89 ++++++ nats/config.go | 97 ++++++ nats/go.mod | 20 ++ nats/go.sum | 24 ++ nats/nats.go | 335 +++++++++++++++++++++ nats/nats_test.go | 265 ++++++++++++++++ 12 files changed, 945 insertions(+), 1 deletion(-) create mode 100644 .github/release-drafter-nats.yml create mode 100644 .github/workflows/release-drafter-nats.yml create mode 100644 .github/workflows/test-nats.yml create mode 100644 nats/README.md create mode 100644 nats/config.go create mode 100644 nats/go.mod create mode 100644 nats/go.sum create mode 100644 nats/nats.go create mode 100644 nats/nats_test.go diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3dcc2585..f270a105 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -145,4 +145,10 @@ updates: labels: - "๐Ÿค– Dependencies" schedule: - interval: "daily" \ No newline at end of file + interval: "daily" + - package-ecosystem: "gomod" + directory: "/nats/" # Location of package manifests + labels: + - "๐Ÿค– Dependencies" + schedule: + interval: "daily" diff --git a/.github/release-drafter-nats.yml b/.github/release-drafter-nats.yml new file mode 100644 index 00000000..2f947eaf --- /dev/null +++ b/.github/release-drafter-nats.yml @@ -0,0 +1,50 @@ +name-template: 'Nats - v$RESOLVED_VERSION' +tag-template: 'nats/v$RESOLVED_VERSION' +tag-prefix: nats/v +include-paths: + - nats +categories: + - title: 'โ— Breaking Changes' + labels: + - 'โ— BreakingChange' + - title: '๐Ÿš€ New' + labels: + - 'โœ๏ธ Feature' + - title: '๐Ÿงน Updates' + labels: + - '๐Ÿงน Updates' + - '๐Ÿค– Dependencies' + - title: '๐Ÿ› Fixes' + labels: + - 'โ˜ข๏ธ Bug' + - title: '๐Ÿ“š Documentation' + labels: + - '๐Ÿ“’ Documentation' +change-template: '- $TITLE (#$NUMBER)' +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. +exclude-contributors: + - dependabot + - dependabot[bot] +version-resolver: + major: + labels: + - 'major' + - 'โ— BreakingChange' + minor: + labels: + - 'minor' + - 'โœ๏ธ Feature' + patch: + labels: + - 'patch' + - '๐Ÿ“’ Documentation' + - 'โ˜ข๏ธ Bug' + - '๐Ÿค– Dependencies' + - '๐Ÿงน Updates' + default: patch +template: | + $CHANGES + + **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...nats/v$RESOLVED_VERSION + + Thank you $CONTRIBUTORS for making this update possible. diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index e7df5333..93b3fb11 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -77,6 +77,13 @@ jobs: options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + jetstream: + image: nats:latest + ports: + - '4222:4222' + options: >- + --jetstream + steps: - name: Fetch Repository uses: actions/checkout@v4 diff --git a/.github/workflows/release-drafter-nats.yml b/.github/workflows/release-drafter-nats.yml new file mode 100644 index 00000000..42ced3ea --- /dev/null +++ b/.github/workflows/release-drafter-nats.yml @@ -0,0 +1,19 @@ +name: Release Drafter Nats +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - master + - main + paths: + - 'nats/**' +jobs: + draft_release_nats: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: release-drafter/release-drafter@v5 + with: + config-name: release-drafter-nats.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-nats.yml b/.github/workflows/test-nats.yml new file mode 100644 index 00000000..afedb443 --- /dev/null +++ b/.github/workflows/test-nats.yml @@ -0,0 +1,29 @@ +on: + push: + branches: + - master + - main + paths: + - 'nats/**' + pull_request: + paths: + - 'nats/**' +name: "Tests Local Storage" +jobs: + Tests: + strategy: + matrix: + go-version: + - 1.19.x + - 1.20.x + - 1.21.x + runs-on: ubuntu-latest + steps: + - name: Fetch Repository + uses: actions/checkout@v4 + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: '${{ matrix.go-version }}' + - name: Test Nats + run: cd ./nats && go test ./... -v -race diff --git a/.gitignore b/.gitignore index 3d503277..bc107497 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ vendor/ vendor /Godeps/ + +# Go specific +go.work* diff --git a/nats/README.md b/nats/README.md new file mode 100644 index 00000000..eb2cf796 --- /dev/null +++ b/nats/README.md @@ -0,0 +1,89 @@ +--- +id: nats +title: Nats +--- + + +![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=nats*) +[![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord) +![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-nats.yml?label=Tests) +![Security](https://img.shields.io/github/actions/workflow/status/gofiber/storage/gosec.yml?label=Security) +![Linter](https://img.shields.io/github/actions/workflow/status/gofiber/storage/linter.yml?label=Linter) + +An NATS storage driver. + +**Note: Requires Go 1.20 and above** + +### Table of Contents + +- [Signatures](#signatures) +- [Installation](#installation) +- [Examples](#examples) +- [Config](#config) +- [Default Config](#default-config) + +### Signatures + +```go +func New(config ...Config) Storage +func (s *Storage) Get(key string) ([]byte, error) +func (s *Storage) Set(key string, val []byte, exp time.Duration) error +func (s *Storage) Delete(key string) error +func (s *Storage) Reset() error +func (s *Storage) Close() error +func (s *Storage) Conn() map[string]entry +func (s *Storage) Keys() ([][]byte, error) +``` + +### Installation + +[NATS KV](https://docs.nats.io/nats-concepts/jetstream/key-value-store) driver is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: + +```bash +go mod init github.com// +``` + +And then install the nats implementation: + +```bash +go get github.com/gofiber/storage/nats +``` + +### Examples + +Import the storage package. + +```go +import "github.com/gofiber/storage/nats" +``` + +You can use the following possibilities to create a storage: + +```go +// Initialize default config +store := nats.New() + +// Initialize custom config +store := nats.New(nats.Config{ + GCInterval: 10 * time.Second, +}) +``` + +### Config + +```go +type Config struct { + // Time before deleting expired keys + // + // Default is 10 * time.Second + GCInterval time.Duration +} +``` + +### Default Config + +```go +var ConfigDefault = Config{ + GCInterval: 10 * time.Second, +} +``` diff --git a/nats/config.go b/nats/config.go new file mode 100644 index 00000000..39b3a4b3 --- /dev/null +++ b/nats/config.go @@ -0,0 +1,97 @@ +package nats + +import ( + "context" + "log/slog" + "os" + + "github.com/nats-io/nats.go" + "github.com/nats-io/nats.go/jetstream" +) + +// Config defines the config for storage. +type Config struct { + // Nats URL, default "nats://127.0.0.1:4222" + URL string + // Nats username + Username string + // Nats password + Password string + // Nats credentials file: https://docs.nats.io/using-nats/developer/connecting/creds + CredentialsFile string + // Nats client name + ClientName string + // Nats retry on failed connect: https://docs.nats.io/using-nats/developer/connecting/reconnect + RetryOnFailedConnect bool + // Nats max reconnects: https://docs.nats.io/using-nats/developer/connecting/reconnect + MaxReconnects int + // Nats context + Context context.Context + // Nats key value config + KeyValueConfig jetstream.KeyValueConfig + Logger *slog.Logger + // Applicable only if Logger is nil. + // Until go 1.22, it is weird to set log level. + // See https://github.com/golang/go/issues/62418 + LogLevel slog.Level +} + +// ConfigDefault is the default config +var ConfigDefault = Config{ + URL: nats.DefaultURL, + // RetryOnFailedConnect: true, + Context: context.Background(), + KeyValueConfig: jetstream.KeyValueConfig{ + Bucket: "fiber_storage", + }, + Logger: slog.New( + slog.NewTextHandler( + os.Stdout, + &slog.HandlerOptions{ + Level: slog.LevelError, + }, + ), + ), + LogLevel: slog.LevelError, +} + +// Helper function to set default values +func configDefault(config ...Config) Config { + // Return default config if nothing provided + if len(config) < 1 { + return ConfigDefault + } + + // Override default config + cfg := config[0] + + // Set default values + if cfg.URL == "" { + cfg.URL = ConfigDefault.URL + } + if cfg.Context == nil { + cfg.Context = ConfigDefault.Context + } + if len(cfg.KeyValueConfig.Bucket) == 0 { + cfg.KeyValueConfig.Bucket = ConfigDefault.KeyValueConfig.Bucket + } + if cfg.Logger == nil { + if cfg.LogLevel != ConfigDefault.LogLevel { + cfg.Logger = slog.New( + slog.NewTextHandler( + os.Stdout, + &slog.HandlerOptions{ + Level: cfg.LogLevel, + }, + ), + ) + } else { + cfg.Logger = ConfigDefault.Logger + } + } + // if !cfg.RetryOnFailedConnect { + // cfg.RetryOnFailedConnect = ConfigDefault.RetryOnFailedConnect + // } + + return cfg +} diff --git a/nats/go.mod b/nats/go.mod new file mode 100644 index 00000000..87ffe490 --- /dev/null +++ b/nats/go.mod @@ -0,0 +1,20 @@ +module github.com/gofiber/storage/nats + +go 1.20 + +require ( + github.com/nats-io/nats.go v1.32.0 + github.com/stretchr/testify v1.8.4 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/klauspost/compress v1.17.2 // indirect + github.com/nats-io/nkeys v0.4.7 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/nats/go.sum b/nats/go.sum new file mode 100644 index 00000000..467f5854 --- /dev/null +++ b/nats/go.sum @@ -0,0 +1,24 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/nats-io/nats.go v1.32.0 h1:Bx9BZS+aXYlxW08k8Gd3yR2s73pV5XSoAQUyp1Kwvp0= +github.com/nats-io/nats.go v1.32.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= +github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= +github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/nats/nats.go b/nats/nats.go new file mode 100644 index 00000000..8f87dba9 --- /dev/null +++ b/nats/nats.go @@ -0,0 +1,335 @@ +package nats + +import ( + "bytes" + "context" + "encoding/gob" + "errors" + "fmt" + "net" + "sync" + "time" + + "github.com/nats-io/nats.go" + "github.com/nats-io/nats.go/jetstream" +) + +// Storage interface that is implemented by storage providers +type Storage struct { + nc *nats.Conn + kv jetstream.KeyValue + err []error + ctx context.Context + cfg Config + mu sync.RWMutex +} + +type entry struct { + Data []byte + Expiry int64 +} + +func init() { + gob.Register(entry{}) +} + +// connectHandler is a helper function to set the initial connect handler +func (s *Storage) connectHandler(nc *nats.Conn) { + s.mu.Lock() + defer s.mu.Unlock() + s.cfg.Logger. + With("url", nc.ConnectedUrlRedacted()). + With("mod", "nats"). + Info("connected") + + var err error + s.kv, err = newNatsKV( + nc, + s.ctx, + s.cfg.KeyValueConfig, + ) + if err != nil { + s.cfg.Logger. + With("err", err). + With("mod", "nats"). + Error("kv not initialized") + s.err = append(s.err, err) + } +} + +// disconnectErrHandler is a helper function to set the disconnect error handler +func (s *Storage) disconnectErrHandler(nc *nats.Conn, err error) { + s.mu.Lock() + defer s.mu.Unlock() + if err != nil { + s.cfg.Logger. + With("err", err). + With("mod", "nats"). + Error("disconnected") + } else { + s.cfg.Logger. + With("mod", "nats"). + Info("disconnected") + } + nc.Opts.RetryOnFailedConnect = true + if err != nil { + s.err = append(s.err, err) + } +} + +// reconnectHandler is a helper function to set the reconnect handler +func (s *Storage) reconnectHandler(nc *nats.Conn) { + s.connectHandler(nc) +} + +// errorHandler is a helper function to set the error handler +func (s *Storage) errorHandler(nc *nats.Conn, sub *nats.Subscription, err error) { + s.mu.Lock() + defer s.mu.Unlock() + s.cfg.Logger. + With("err", err). + With("sub", sub.Subject). + With("mod", "nats"). + Error("error") + if err != nil { + s.err = append(s.err, fmt.Errorf("subject %q: %w", sub.Subject, err)) + } +} + +// closedHandler is a helper function to set the closed handler +func (s *Storage) closedHandler(nc *nats.Conn) { + s.mu.RLock() + defer s.mu.RUnlock() + s.cfg.Logger. + With("mod", "nats"). + Info("closed") +} + +func newNatsKV(nc *nats.Conn, ctx context.Context, keyValueConfig jetstream.KeyValueConfig) (jetstream.KeyValue, error) { + js, err := jetstream.New(nc) + if err != nil { + return nil, fmt.Errorf("get jetstream: %w", err) + } + jskv, err := js.KeyValue(ctx, keyValueConfig.Bucket) + if err != nil { + if errors.Is(err, jetstream.ErrBucketNotFound) { + jskv, err = js.CreateKeyValue(ctx, keyValueConfig) + if err != nil { + return nil, fmt.Errorf("jetstream: create kv: %w", err) + } + } else { + return nil, fmt.Errorf("jetstream: get kv: %w", err) + } + } + + return jskv, nil +} + +// New creates a new nats kv storage +func New(config ...Config) *Storage { + // Set default config + cfg := configDefault(config...) + + storage := &Storage{ + ctx: cfg.Context, + cfg: cfg, + } + + var optionalUserCreds nats.Option + if len(cfg.CredentialsFile) > 0 { + optionalUserCreds = nats.UserCredentials(cfg.CredentialsFile) + } + var optionalUserInfo nats.Option + if len(cfg.Username) > 0 { + optionalUserInfo = nats.UserInfo(cfg.Username, cfg.Password) + } + + // Connect to NATS with minimal options + var err error + storage.nc, err = nats.Connect( + cfg.URL, + nats.Name(cfg.ClientName), + optionalUserInfo, + optionalUserCreds, + nats.RetryOnFailedConnect(cfg.RetryOnFailedConnect), + nats.MaxReconnects(cfg.MaxReconnects), + nats.ConnectHandler(storage.connectHandler), + nats.DisconnectErrHandler(storage.disconnectErrHandler), + nats.ReconnectHandler(storage.reconnectHandler), + nats.ErrorHandler(storage.errorHandler), + nats.ClosedHandler(storage.closedHandler), + ) + + if opErr, ok := err.(*net.OpError); ok && cfg.RetryOnFailedConnect { + if opErr.Op != "dial" { + panic(err) + } + } else if err != nil { + panic(err) + } + + // TODO improve this crude way to wait for the connection to be established + time.Sleep(100 * time.Millisecond) + + return storage +} + +// Get value by key +func (s *Storage) Get(key string) ([]byte, error) { + if len(key) <= 0 { + return nil, nil + } + + s.mu.RLock() + kv := s.kv + s.mu.RUnlock() + if kv == nil { + return nil, fmt.Errorf("kv not initialized: %v", s.err) + } + + v, err := kv.Get(s.ctx, key) + if err != nil { + if errors.Is(err, jetstream.ErrKeyNotFound) { + return nil, nil + } + return nil, fmt.Errorf("get: %w", err) + } + + e := entry{} + err = gob.NewDecoder( + bytes.NewBuffer(v.Value())). + Decode(&e) + if err != nil || e.Expiry <= time.Now().Unix() { + _ = kv.Delete(s.ctx, key) + return nil, nil + } + + return e.Data, nil +} + +// Set key with value +func (s *Storage) Set(key string, val []byte, exp time.Duration) error { + if len(key) <= 0 || len(val) <= 0 { + return nil + } + + s.mu.RLock() + kv := s.kv + s.mu.RUnlock() + if kv == nil { + return fmt.Errorf("kv not initialized: %v", s.err) + } + + // expiry + var expSeconds int64 + if exp != 0 { + expSeconds = time.Now().Add(exp).Unix() + } + // encode + e := new(bytes.Buffer) + err := gob.NewEncoder(e).Encode(entry{ + Data: val, + Expiry: expSeconds, + }) + if err != nil { + return fmt.Errorf("encode: %w", err) + } + + // set + _, err = kv.Put(s.ctx, key, e.Bytes()) + if errors.Is(err, jetstream.ErrKeyNotFound) { + _, err := kv.Create(s.ctx, key, e.Bytes()) + if err != nil { + return fmt.Errorf("create: %w", err) + } + } + + return err +} + +// Delete key by key +func (s *Storage) Delete(key string) error { + if len(key) <= 0 { + return nil + } + + s.mu.RLock() + kv := s.kv + s.mu.RUnlock() + + if kv == nil { + return fmt.Errorf("kv not initialized: %v", s.err) + } + + return kv.Delete(s.ctx, key) +} + +// Reset all keys +func (s *Storage) Reset() error { + js, err := jetstream.New(s.nc) + if err != nil { + return fmt.Errorf("get jetstream: %w", err) + } + + // Delete the bucket + err = js.DeleteKeyValue(s.ctx, s.cfg.KeyValueConfig.Bucket) + if err != nil { + return fmt.Errorf("delete kv: %w", err) + } + + // Create the bucket + s.mu.Lock() + s.kv, err = newNatsKV( + s.nc, + s.ctx, + s.cfg.KeyValueConfig, + ) + s.mu.Unlock() + if err != nil { + s.err = []error{err} + return err + } + + s.err = nil + return nil +} + +// Close the nats connection +func (s *Storage) Close() error { + s.mu.RLock() + s.nc.Close() + s.mu.RUnlock() + return nil +} + +// Return database client +func (s *Storage) Conn() (*nats.Conn, jetstream.KeyValue) { + s.mu.RLock() + defer s.mu.RUnlock() + return s.nc, s.kv +} + +// Return all the keys +func (s *Storage) Keys() ([]string, error) { + s.mu.RLock() + kv := s.kv + s.mu.RUnlock() + keyLister, err := kv.ListKeys(s.ctx) + + if err != nil { + return nil, fmt.Errorf("keys: %w", err) + } + + var keys []string + for key := range keyLister.Keys() { + keys = append(keys, key) + } + _ = keyLister.Stop() + + // Double check if no valid keys were found + if len(keys) == 0 { + return nil, nil + } + + return keys, nil +} diff --git a/nats/nats_test.go b/nats/nats_test.go new file mode 100644 index 00000000..4f4861d5 --- /dev/null +++ b/nats/nats_test.go @@ -0,0 +1,265 @@ +package nats + +import ( + "log/slog" + "testing" + "time" + + "github.com/nats-io/nats.go/jetstream" + "github.com/stretchr/testify/require" +) + +var config = Config{ + MaxReconnects: 1, + LogLevel: slog.LevelError, + KeyValueConfig: jetstream.KeyValueConfig{ + Bucket: "test", + Storage: jetstream.MemoryStorage, + }, +} + +func Test_Storage_Nats_Set(t *testing.T) { + var ( + testStore = New(config) + key = "john" + val = []byte("doe") + ) + defer testStore.Close() + + err := testStore.Set(key, val, 0) + require.NoError(t, err) + + keys, err := testStore.Keys() + require.NoError(t, err) + require.Len(t, keys, 1) +} + +func Test_Storage_Nats_Set_Overwrite(t *testing.T) { + var ( + testStore = New(config) + key = "john" + val1 = []byte("doe") + val2 = []byte("overwritten") + ) + defer testStore.Close() + + err := testStore.Set(key, val1, 0) + require.NoError(t, err) + + err = testStore.Set(key, val2, 30*time.Second) + require.NoError(t, err) + v, err := testStore.Get(key) + require.NoError(t, err) + require.Equal(t, val2, v) + + keys, err := testStore.Keys() + require.NoError(t, err) + require.Len(t, keys, 1) +} + +func Test_Storage_Nats_Get(t *testing.T) { + var ( + testStore = New(config) + key = "john" + val = []byte("doe") + ) + defer testStore.Close() + + err := testStore.Set(key, val, 30*time.Second) + require.NoError(t, err) + + result, err := testStore.Get(key) + require.NoError(t, err) + require.Equal(t, val, result) + + keys, err := testStore.Keys() + require.NoError(t, err) + require.Len(t, keys, 1) +} + +func Test_Storage_Nats_Set_Expiration(t *testing.T) { + var ( + testStore = New(config) + key = "john" + val = []byte("doe") + exp = 1 * time.Second + ) + defer testStore.Close() + + err := testStore.Set(key, val, exp) + require.NoError(t, err) + + time.Sleep(1100 * time.Millisecond) + + result, err := testStore.Get(key) + require.NoError(t, err) + require.Zero(t, len(result)) + + keys, err := testStore.Keys() + require.NoError(t, err) + require.Nil(t, keys) +} + +func Test_Storage_Nats_Set_Long_Expiration_with_Keys(t *testing.T) { + var ( + testStore = New(config) + key = "john" + val = []byte("doe") + exp = 5 * time.Second + ) + defer testStore.Close() + + keys, err := testStore.Keys() + require.NoError(t, err) + require.Nil(t, keys) + + err = testStore.Set(key, val, exp) + require.NoError(t, err) + + time.Sleep(1100 * time.Millisecond) + + keys, err = testStore.Keys() + require.NoError(t, err) + require.Len(t, keys, 1) + + time.Sleep(4000 * time.Millisecond) + result, err := testStore.Get(key) + require.NoError(t, err) + require.Zero(t, len(result)) + + keys, err = testStore.Keys() + require.NoError(t, err) + require.Nil(t, keys) +} + +func Test_Storage_Nats_Get_NotExist(t *testing.T) { + testStore := New(config) + defer testStore.Close() + + result, err := testStore.Get("notexist") + require.NoError(t, err) + require.Zero(t, len(result)) + + keys, err := testStore.Keys() + require.NoError(t, err) + require.Nil(t, keys) +} + +func Test_Storage_Nats_Delete(t *testing.T) { + var ( + testStore = New(config) + key = "john" + val = []byte("doe") + ) + defer testStore.Close() + + err := testStore.Set(key, val, 0) + require.NoError(t, err) + + keys, err := testStore.Keys() + require.NoError(t, err) + require.Len(t, keys, 1) + + err = testStore.Delete(key) + require.NoError(t, err) + + result, err := testStore.Get(key) + require.NoError(t, err) + require.Zero(t, len(result)) + + keys, err = testStore.Keys() + require.NoError(t, err) + require.Nil(t, keys) +} + +func Test_Storage_Nats_Reset(t *testing.T) { + testStore := New(config) + defer testStore.Close() + val := []byte("doe") + + err := testStore.Set("john1", val, 0) + require.NoError(t, err) + + err = testStore.Set("john2", val, 0) + require.NoError(t, err) + + keys, err := testStore.Keys() + require.NoError(t, err) + require.Len(t, keys, 2) + + err = testStore.Reset() + require.NoError(t, err) + + result, err := testStore.Get("john1") + require.NoError(t, err) + require.Zero(t, len(result)) + + result, err = testStore.Get("john2") + require.NoError(t, err) + require.Zero(t, len(result)) + + keys, err = testStore.Keys() + require.NoError(t, err) + require.Nil(t, keys) +} + +func Test_Storage_Nats_Close(t *testing.T) { + testStore := New(config) + require.Nil(t, testStore.Close()) +} + +func Test_Storage_Nats_Conn(t *testing.T) { + testStore := New(config) + defer testStore.Close() + n, k := testStore.Conn() + require.NotNil(t, n) + require.NotNil(t, k) +} + +func Benchmark_Nats_Set(b *testing.B) { + testStore := New(config) + defer testStore.Close() + + b.ReportAllocs() + b.ResetTimer() + + var err error + for i := 0; i < b.N; i++ { + err = testStore.Set("john", []byte("doe"), 0) + } + + require.NoError(b, err) +} + +func Benchmark_Nats_Get(b *testing.B) { + testStore := New(config) + defer testStore.Close() + + err := testStore.Set("john", []byte("doe"), 0) + require.NoError(b, err) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, err = testStore.Get("john") + } + + require.NoError(b, err) +} + +func Benchmark_Nats_SetAndDelete(b *testing.B) { + testStore := New(config) + defer testStore.Close() + + b.ReportAllocs() + b.ResetTimer() + + var err error + for i := 0; i < b.N; i++ { + _ = testStore.Set("john", []byte("doe"), 0) + err = testStore.Delete("john") + } + + require.NoError(b, err) +} From 0b3aad8e47cc6b8a6ea98a53b4369a250756b8ab Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Mon, 29 Jan 2024 23:15:41 +0200 Subject: [PATCH 02/32] workflows: alternative nats run --- .github/workflows/benchmark.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 93b3fb11..bb498d82 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -77,12 +77,6 @@ jobs: options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 - jetstream: - image: nats:latest - ports: - - '4222:4222' - options: >- - --jetstream steps: - name: Fetch Repository @@ -139,6 +133,11 @@ jobs: run: | redis-server --port 6379 & + - name: Install NATS + run: | + docker run -d --name nats-jetstream -p 4222:4222 nats:latest -- --jetstream + sleep 2 + - name: Run Benchmarks run: | set -o pipefail From c80fa8f0c71cebd3957922342425ff79d97f484c Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Mon, 29 Jan 2024 23:26:18 +0200 Subject: [PATCH 03/32] go 1.21 --- .github/workflows/test-nats.yml | 2 -- nats/README.md | 2 +- nats/go.mod | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-nats.yml b/.github/workflows/test-nats.yml index afedb443..c469a293 100644 --- a/.github/workflows/test-nats.yml +++ b/.github/workflows/test-nats.yml @@ -14,8 +14,6 @@ jobs: strategy: matrix: go-version: - - 1.19.x - - 1.20.x - 1.21.x runs-on: ubuntu-latest steps: diff --git a/nats/README.md b/nats/README.md index eb2cf796..21175353 100644 --- a/nats/README.md +++ b/nats/README.md @@ -12,7 +12,7 @@ title: Nats An NATS storage driver. -**Note: Requires Go 1.20 and above** +**Note: Requires Go 1.21 and above because of slog** ### Table of Contents diff --git a/nats/go.mod b/nats/go.mod index 87ffe490..2ae2d548 100644 --- a/nats/go.mod +++ b/nats/go.mod @@ -1,6 +1,6 @@ module github.com/gofiber/storage/nats -go 1.20 +go 1.21 require ( github.com/nats-io/nats.go v1.32.0 From 715c4816f57f7c336eeab4c8b69f9971aeec021b Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Mon, 29 Jan 2024 23:29:28 +0200 Subject: [PATCH 04/32] github actions: run nats server for tests --- .github/workflows/benchmark.yml | 2 +- .github/workflows/test-nats.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index bb498d82..b9438eaf 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -133,7 +133,7 @@ jobs: run: | redis-server --port 6379 & - - name: Install NATS + - name: Run NATS run: | docker run -d --name nats-jetstream -p 4222:4222 nats:latest -- --jetstream sleep 2 diff --git a/.github/workflows/test-nats.yml b/.github/workflows/test-nats.yml index c469a293..b866c8e4 100644 --- a/.github/workflows/test-nats.yml +++ b/.github/workflows/test-nats.yml @@ -23,5 +23,9 @@ jobs: uses: actions/setup-go@v5 with: go-version: '${{ matrix.go-version }}' + - name: Run NATS + run: | + docker run -d --name nats-jetstream -p 4222:4222 nats:latest -- --jetstream + sleep 2 - name: Test Nats run: cd ./nats && go test ./... -v -race From 2fe2a2c80cf9c99bd8ce98b59ec8d9357b81caca Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Mon, 29 Jan 2024 23:38:08 +0200 Subject: [PATCH 05/32] use go 1.21 --- .github/workflows/benchmark.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index b9438eaf..0a1c1674 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -86,7 +86,7 @@ jobs: uses: actions/setup-go@v5 with: # NOTE: Keep this in sync with the version from go.mod - go-version: "1.20.x" + go-version: "1.21.x" - name: Install Azurite run: | From febffbf336cf0dd46ddf5be95146cc9288cf2b6e Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 00:02:18 +0200 Subject: [PATCH 06/32] increase wait for connection --- nats/nats.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nats/nats.go b/nats/nats.go index 8f87dba9..d2bc16eb 100644 --- a/nats/nats.go +++ b/nats/nats.go @@ -169,7 +169,7 @@ func New(config ...Config) *Storage { } // TODO improve this crude way to wait for the connection to be established - time.Sleep(100 * time.Millisecond) + time.Sleep(nats.DefaultReconnectWait) return storage } From 3f5e85a62b39278a13986f0f51427f5ab05f3c95 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 00:06:45 +0200 Subject: [PATCH 07/32] Update test-nats.yml --- .github/workflows/test-nats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-nats.yml b/.github/workflows/test-nats.yml index b866c8e4..b464cf19 100644 --- a/.github/workflows/test-nats.yml +++ b/.github/workflows/test-nats.yml @@ -26,6 +26,6 @@ jobs: - name: Run NATS run: | docker run -d --name nats-jetstream -p 4222:4222 nats:latest -- --jetstream - sleep 2 + sleep 10 - name: Test Nats run: cd ./nats && go test ./... -v -race From dec6423afcf3108b011fab32e359d1e5e880d914 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 00:12:00 +0200 Subject: [PATCH 08/32] try to run just nats benchmark --- .github/workflows/benchmark.yml | 206 ++++++++++++++++---------------- 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 0a1c1674..10116187 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -19,64 +19,64 @@ name: Benchmark jobs: Compare: runs-on: ubuntu-latest - services: - arangodb: - image: 'arangodb:latest' - env: - ARANGO_NO_AUTH: 1 - ports: - - '8529:8529' - dynamodb: - image: 'amazon/dynamodb-local:latest' - ports: - - '8000:8000' - memcached: - image: 'memcached:latest' - ports: - - '11211:11211' - mongo: - image: 'mongo:latest' - ports: - - '27017:27017' - mssql: - image: 'mcmoe/mssqldocker:latest' - ports: - - '1433:1433' - env: - ACCEPT_EULA: Y - SA_PASSWORD: MsSql!1234 - MSSQL_DB: master - MSSQL_USER: sa - MSSQL_PASSWORD: MsSql!1234 - options: >- - --health-cmd "/opt/mssql-tools/bin/sqlcmd -U sa -P $SA_PASSWORD -Q 'select 1' -b -o /dev/null" - --health-interval 1s - --health-timeout 30s - --health-start-period 10s - --health-retries 20 - mysql: - image: 'mysql:latest' - env: - MYSQL_DATABASE: fiber - MYSQL_USER: username - MYSQL_PASSWORD: password - MYSQL_ROOT_PASSWORD: password - ports: - - '3306:3306' - options: >- - --health-cmd "mysqladmin ping" --health-interval 10s --health-timeout - 5s --health-retries 5 - postgres: - image: 'postgres:latest' - ports: - - '5432:5432' - env: - POSTGRES_DB: fiber - POSTGRES_USER: username - POSTGRES_PASSWORD: "pass#w%rd" - options: >- - --health-cmd pg_isready --health-interval 10s --health-timeout 5s - --health-retries 5 + # services: + # arangodb: + # image: 'arangodb:latest' + # env: + # ARANGO_NO_AUTH: 1 + # ports: + # - '8529:8529' + # dynamodb: + # image: 'amazon/dynamodb-local:latest' + # ports: + # - '8000:8000' + # memcached: + # image: 'memcached:latest' + # ports: + # - '11211:11211' + # mongo: + # image: 'mongo:latest' + # ports: + # - '27017:27017' + # mssql: + # image: 'mcmoe/mssqldocker:latest' + # ports: + # - '1433:1433' + # env: + # ACCEPT_EULA: Y + # SA_PASSWORD: MsSql!1234 + # MSSQL_DB: master + # MSSQL_USER: sa + # MSSQL_PASSWORD: MsSql!1234 + # options: >- + # --health-cmd "/opt/mssql-tools/bin/sqlcmd -U sa -P $SA_PASSWORD -Q 'select 1' -b -o /dev/null" + # --health-interval 1s + # --health-timeout 30s + # --health-start-period 10s + # --health-retries 20 + # mysql: + # image: 'mysql:latest' + # env: + # MYSQL_DATABASE: fiber + # MYSQL_USER: username + # MYSQL_PASSWORD: password + # MYSQL_ROOT_PASSWORD: password + # ports: + # - '3306:3306' + # options: >- + # --health-cmd "mysqladmin ping" --health-interval 10s --health-timeout + # 5s --health-retries 5 + # postgres: + # image: 'postgres:latest' + # ports: + # - '5432:5432' + # env: + # POSTGRES_DB: fiber + # POSTGRES_USER: username + # POSTGRES_PASSWORD: "pass#w%rd" + # options: >- + # --health-cmd pg_isready --health-interval 10s --health-timeout 5s + # --health-retries 5 steps: - name: Fetch Repository @@ -88,50 +88,50 @@ jobs: # NOTE: Keep this in sync with the version from go.mod go-version: "1.21.x" - - name: Install Azurite - run: | - docker run -d -p 10000:10000 mcr.microsoft.com/azure-storage/azurite azurite-blob --blobHost 0.0.0.0 --blobPort 10000 - - - name: Install Coherence - run: | - docker run -d -p 1408:1408 -p 30000:30000 ghcr.io/oracle/coherence-ce:22.06.5 - sleep 30 - - - name: Install couchbase - run: | - docker run --name couchbase -d -p 8091-8097:8091-8097 -p 9123:9123 -p 11207:11207 -p 11210:11210 -p 11280:11280 -p 18091-18097:18091-18097 couchbase:enterprise-7.1.1 - sleep 10 - docker exec --tty couchbase couchbase-cli cluster-init -c localhost:8091 --cluster-username admin --cluster-password 123456 --cluster-ramsize 256 --services data - sleep 10 - docker exec --tty couchbase couchbase-cli bucket-create -c localhost:8091 --username admin --password 123456 --bucket fiber_storage --bucket-type couchbase --bucket-ramsize 100 --enable-flush 1 - - - name: Install etcd - run: | - docker run -d --name Etcd-server \ - --publish 2379:2379 \ - --publish 2380:2380 \ - --env ALLOW_NONE_AUTHENTICATION=yes \ - --env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-server:2379 \ - bitnami/etcd:latest - - - name: Install MinIO - run: | - docker run -d --restart always -p 9000:9000 --name storage-minio -e MINIO_ROOT_USER='minio-user' -e MINIO_ROOT_PASSWORD='minio-password' minio/minio server /data - - - name: Install ScyllaDb - run: | - docker run --name scylladb -p 9042:9042 -p 19042:19042 -p 9160:9160 -p 7000:7000 -p 7001:7001 -p 7199:7199 -p 9180:9180 -d scylladb/scylla:latest --broadcast-address 127.0.0.1 --listen-address 0.0.0.0 --broadcast-rpc-address 127.0.0.1 - sleep 15 # Wait for ScyllaDb to initialize - - - name: Setup Redis - uses: shogo82148/actions-setup-redis@v1 - with: - redis-version: '7.x' - auto-start: 'false' - - - name: Run Redis - run: | - redis-server --port 6379 & + # - name: Install Azurite + # run: | + # docker run -d -p 10000:10000 mcr.microsoft.com/azure-storage/azurite azurite-blob --blobHost 0.0.0.0 --blobPort 10000 + + # - name: Install Coherence + # run: | + # docker run -d -p 1408:1408 -p 30000:30000 ghcr.io/oracle/coherence-ce:22.06.5 + # sleep 30 + + # - name: Install couchbase + # run: | + # docker run --name couchbase -d -p 8091-8097:8091-8097 -p 9123:9123 -p 11207:11207 -p 11210:11210 -p 11280:11280 -p 18091-18097:18091-18097 couchbase:enterprise-7.1.1 + # sleep 10 + # docker exec --tty couchbase couchbase-cli cluster-init -c localhost:8091 --cluster-username admin --cluster-password 123456 --cluster-ramsize 256 --services data + # sleep 10 + # docker exec --tty couchbase couchbase-cli bucket-create -c localhost:8091 --username admin --password 123456 --bucket fiber_storage --bucket-type couchbase --bucket-ramsize 100 --enable-flush 1 + + # - name: Install etcd + # run: | + # docker run -d --name Etcd-server \ + # --publish 2379:2379 \ + # --publish 2380:2380 \ + # --env ALLOW_NONE_AUTHENTICATION=yes \ + # --env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-server:2379 \ + # bitnami/etcd:latest + + # - name: Install MinIO + # run: | + # docker run -d --restart always -p 9000:9000 --name storage-minio -e MINIO_ROOT_USER='minio-user' -e MINIO_ROOT_PASSWORD='minio-password' minio/minio server /data + + # - name: Install ScyllaDb + # run: | + # docker run --name scylladb -p 9042:9042 -p 19042:19042 -p 9160:9160 -p 7000:7000 -p 7001:7001 -p 7199:7199 -p 9180:9180 -d scylladb/scylla:latest --broadcast-address 127.0.0.1 --listen-address 0.0.0.0 --broadcast-rpc-address 127.0.0.1 + # sleep 15 # Wait for ScyllaDb to initialize + + # - name: Setup Redis + # uses: shogo82148/actions-setup-redis@v1 + # with: + # redis-version: '7.x' + # auto-start: 'false' + + # - name: Run Redis + # run: | + # redis-server --port 6379 & - name: Run NATS run: | @@ -141,7 +141,7 @@ jobs: - name: Run Benchmarks run: | set -o pipefail - for d in */ ; do + for d in nats/ ; do [[ $d == "tls/" ]] && continue cd "$d" From 57e9fe0d0b55a2db615f8eaa2f73469e51159919 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 00:22:08 +0200 Subject: [PATCH 09/32] debug benchmark --- .github/workflows/benchmark.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 10116187..8e40c1f0 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -137,6 +137,8 @@ jobs: run: | docker run -d --name nats-jetstream -p 4222:4222 nats:latest -- --jetstream sleep 2 + docker logs nats-jetstream + nc -zv localhost 4222 || true - name: Run Benchmarks run: | From b544a21f5f5b65bf5ba576ea21ab66d000a503bf Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 00:24:07 +0200 Subject: [PATCH 10/32] debug benchmark --- .github/workflows/benchmark.yml | 2 +- .github/workflows/test-nats.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 8e40c1f0..a5249767 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -135,7 +135,7 @@ jobs: - name: Run NATS run: | - docker run -d --name nats-jetstream -p 4222:4222 nats:latest -- --jetstream + docker run -d --name nats-jetstream -p 4222:4222 nats:latest --jetstream sleep 2 docker logs nats-jetstream nc -zv localhost 4222 || true diff --git a/.github/workflows/test-nats.yml b/.github/workflows/test-nats.yml index b464cf19..a646ac2a 100644 --- a/.github/workflows/test-nats.yml +++ b/.github/workflows/test-nats.yml @@ -25,7 +25,7 @@ jobs: go-version: '${{ matrix.go-version }}' - name: Run NATS run: | - docker run -d --name nats-jetstream -p 4222:4222 nats:latest -- --jetstream - sleep 10 + docker run -d --name nats-jetstream -p 4222:4222 nats:latest --jetstream + sleep 2 - name: Test Nats run: cd ./nats && go test ./... -v -race From 4159aa7e6ea7cc11df01753aeb7bf461490cedf7 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 00:27:46 +0200 Subject: [PATCH 11/32] revert debug --- .github/workflows/benchmark.yml | 208 ++++++++++++++++---------------- nats/nats.go | 2 +- 2 files changed, 104 insertions(+), 106 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index a5249767..243b2879 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -19,64 +19,64 @@ name: Benchmark jobs: Compare: runs-on: ubuntu-latest - # services: - # arangodb: - # image: 'arangodb:latest' - # env: - # ARANGO_NO_AUTH: 1 - # ports: - # - '8529:8529' - # dynamodb: - # image: 'amazon/dynamodb-local:latest' - # ports: - # - '8000:8000' - # memcached: - # image: 'memcached:latest' - # ports: - # - '11211:11211' - # mongo: - # image: 'mongo:latest' - # ports: - # - '27017:27017' - # mssql: - # image: 'mcmoe/mssqldocker:latest' - # ports: - # - '1433:1433' - # env: - # ACCEPT_EULA: Y - # SA_PASSWORD: MsSql!1234 - # MSSQL_DB: master - # MSSQL_USER: sa - # MSSQL_PASSWORD: MsSql!1234 - # options: >- - # --health-cmd "/opt/mssql-tools/bin/sqlcmd -U sa -P $SA_PASSWORD -Q 'select 1' -b -o /dev/null" - # --health-interval 1s - # --health-timeout 30s - # --health-start-period 10s - # --health-retries 20 - # mysql: - # image: 'mysql:latest' - # env: - # MYSQL_DATABASE: fiber - # MYSQL_USER: username - # MYSQL_PASSWORD: password - # MYSQL_ROOT_PASSWORD: password - # ports: - # - '3306:3306' - # options: >- - # --health-cmd "mysqladmin ping" --health-interval 10s --health-timeout - # 5s --health-retries 5 - # postgres: - # image: 'postgres:latest' - # ports: - # - '5432:5432' - # env: - # POSTGRES_DB: fiber - # POSTGRES_USER: username - # POSTGRES_PASSWORD: "pass#w%rd" - # options: >- - # --health-cmd pg_isready --health-interval 10s --health-timeout 5s - # --health-retries 5 + services: + arangodb: + image: 'arangodb:latest' + env: + ARANGO_NO_AUTH: 1 + ports: + - '8529:8529' + dynamodb: + image: 'amazon/dynamodb-local:latest' + ports: + - '8000:8000' + memcached: + image: 'memcached:latest' + ports: + - '11211:11211' + mongo: + image: 'mongo:latest' + ports: + - '27017:27017' + mssql: + image: 'mcmoe/mssqldocker:latest' + ports: + - '1433:1433' + env: + ACCEPT_EULA: Y + SA_PASSWORD: MsSql!1234 + MSSQL_DB: master + MSSQL_USER: sa + MSSQL_PASSWORD: MsSql!1234 + options: >- + --health-cmd "/opt/mssql-tools/bin/sqlcmd -U sa -P $SA_PASSWORD -Q 'select 1' -b -o /dev/null" + --health-interval 1s + --health-timeout 30s + --health-start-period 10s + --health-retries 20 + mysql: + image: 'mysql:latest' + env: + MYSQL_DATABASE: fiber + MYSQL_USER: username + MYSQL_PASSWORD: password + MYSQL_ROOT_PASSWORD: password + ports: + - '3306:3306' + options: >- + --health-cmd "mysqladmin ping" --health-interval 10s --health-timeout + 5s --health-retries 5 + postgres: + image: 'postgres:latest' + ports: + - '5432:5432' + env: + POSTGRES_DB: fiber + POSTGRES_USER: username + POSTGRES_PASSWORD: "pass#w%rd" + options: >- + --health-cmd pg_isready --health-interval 10s --health-timeout 5s + --health-retries 5 steps: - name: Fetch Repository @@ -88,62 +88,60 @@ jobs: # NOTE: Keep this in sync with the version from go.mod go-version: "1.21.x" - # - name: Install Azurite - # run: | - # docker run -d -p 10000:10000 mcr.microsoft.com/azure-storage/azurite azurite-blob --blobHost 0.0.0.0 --blobPort 10000 - - # - name: Install Coherence - # run: | - # docker run -d -p 1408:1408 -p 30000:30000 ghcr.io/oracle/coherence-ce:22.06.5 - # sleep 30 - - # - name: Install couchbase - # run: | - # docker run --name couchbase -d -p 8091-8097:8091-8097 -p 9123:9123 -p 11207:11207 -p 11210:11210 -p 11280:11280 -p 18091-18097:18091-18097 couchbase:enterprise-7.1.1 - # sleep 10 - # docker exec --tty couchbase couchbase-cli cluster-init -c localhost:8091 --cluster-username admin --cluster-password 123456 --cluster-ramsize 256 --services data - # sleep 10 - # docker exec --tty couchbase couchbase-cli bucket-create -c localhost:8091 --username admin --password 123456 --bucket fiber_storage --bucket-type couchbase --bucket-ramsize 100 --enable-flush 1 - - # - name: Install etcd - # run: | - # docker run -d --name Etcd-server \ - # --publish 2379:2379 \ - # --publish 2380:2380 \ - # --env ALLOW_NONE_AUTHENTICATION=yes \ - # --env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-server:2379 \ - # bitnami/etcd:latest - - # - name: Install MinIO - # run: | - # docker run -d --restart always -p 9000:9000 --name storage-minio -e MINIO_ROOT_USER='minio-user' -e MINIO_ROOT_PASSWORD='minio-password' minio/minio server /data - - # - name: Install ScyllaDb - # run: | - # docker run --name scylladb -p 9042:9042 -p 19042:19042 -p 9160:9160 -p 7000:7000 -p 7001:7001 -p 7199:7199 -p 9180:9180 -d scylladb/scylla:latest --broadcast-address 127.0.0.1 --listen-address 0.0.0.0 --broadcast-rpc-address 127.0.0.1 - # sleep 15 # Wait for ScyllaDb to initialize - - # - name: Setup Redis - # uses: shogo82148/actions-setup-redis@v1 - # with: - # redis-version: '7.x' - # auto-start: 'false' - - # - name: Run Redis - # run: | - # redis-server --port 6379 & + - name: Install Azurite + run: | + docker run -d -p 10000:10000 mcr.microsoft.com/azure-storage/azurite azurite-blob --blobHost 0.0.0.0 --blobPort 10000 + + - name: Install Coherence + run: | + docker run -d -p 1408:1408 -p 30000:30000 ghcr.io/oracle/coherence-ce:22.06.5 + sleep 30 + + - name: Install couchbase + run: | + docker run --name couchbase -d -p 8091-8097:8091-8097 -p 9123:9123 -p 11207:11207 -p 11210:11210 -p 11280:11280 -p 18091-18097:18091-18097 couchbase:enterprise-7.1.1 + sleep 10 + docker exec --tty couchbase couchbase-cli cluster-init -c localhost:8091 --cluster-username admin --cluster-password 123456 --cluster-ramsize 256 --services data + sleep 10 + docker exec --tty couchbase couchbase-cli bucket-create -c localhost:8091 --username admin --password 123456 --bucket fiber_storage --bucket-type couchbase --bucket-ramsize 100 --enable-flush 1 + + - name: Install etcd + run: | + docker run -d --name Etcd-server \ + --publish 2379:2379 \ + --publish 2380:2380 \ + --env ALLOW_NONE_AUTHENTICATION=yes \ + --env ETCD_ADVERTISE_CLIENT_URLS=http://etcd-server:2379 \ + bitnami/etcd:latest + + - name: Install MinIO + run: | + docker run -d --restart always -p 9000:9000 --name storage-minio -e MINIO_ROOT_USER='minio-user' -e MINIO_ROOT_PASSWORD='minio-password' minio/minio server /data + + - name: Install ScyllaDb + run: | + docker run --name scylladb -p 9042:9042 -p 19042:19042 -p 9160:9160 -p 7000:7000 -p 7001:7001 -p 7199:7199 -p 9180:9180 -d scylladb/scylla:latest --broadcast-address 127.0.0.1 --listen-address 0.0.0.0 --broadcast-rpc-address 127.0.0.1 + sleep 15 # Wait for ScyllaDb to initialize + + - name: Setup Redis + uses: shogo82148/actions-setup-redis@v1 + with: + redis-version: '7.x' + auto-start: 'false' + + - name: Run Redis + run: | + redis-server --port 6379 & - name: Run NATS run: | docker run -d --name nats-jetstream -p 4222:4222 nats:latest --jetstream sleep 2 - docker logs nats-jetstream - nc -zv localhost 4222 || true - name: Run Benchmarks run: | set -o pipefail - for d in nats/ ; do + for d in */ ; do [[ $d == "tls/" ]] && continue cd "$d" diff --git a/nats/nats.go b/nats/nats.go index d2bc16eb..8f87dba9 100644 --- a/nats/nats.go +++ b/nats/nats.go @@ -169,7 +169,7 @@ func New(config ...Config) *Storage { } // TODO improve this crude way to wait for the connection to be established - time.Sleep(nats.DefaultReconnectWait) + time.Sleep(100 * time.Millisecond) return storage } From 3ccb4c9d902b6516c0ba78906e3e1457674afb94 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 00:41:01 +0200 Subject: [PATCH 12/32] Update README.md --- nats/README.md | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/nats/README.md b/nats/README.md index 21175353..75735344 100644 --- a/nats/README.md +++ b/nats/README.md @@ -73,10 +73,29 @@ store := nats.New(nats.Config{ ```go type Config struct { - // Time before deleting expired keys - // - // Default is 10 * time.Second - GCInterval time.Duration + // Nats URL, default "nats://127.0.0.1:4222" + URL string + // Nats username + Username string + // Nats password + Password string + // Nats credentials file: https://docs.nats.io/using-nats/developer/connecting/creds + CredentialsFile string + // Nats client name + ClientName string + // Nats retry on failed connect: https://docs.nats.io/using-nats/developer/connecting/reconnect + RetryOnFailedConnect bool + // Nats max reconnects: https://docs.nats.io/using-nats/developer/connecting/reconnect + MaxReconnects int + // Nats context + Context context.Context + // Nats key value config + KeyValueConfig jetstream.KeyValueConfig + Logger *slog.Logger + // Applicable only if Logger is nil. + // Until go 1.22, it is weird to set log level. + // See https://github.com/golang/go/issues/62418 + LogLevel slog.Level } ``` @@ -84,6 +103,20 @@ type Config struct { ```go var ConfigDefault = Config{ - GCInterval: 10 * time.Second, + URL: nats.DefaultURL, + // RetryOnFailedConnect: true, + Context: context.Background(), + KeyValueConfig: jetstream.KeyValueConfig{ + Bucket: "fiber_storage", + }, + Logger: slog.New( + slog.NewTextHandler( + os.Stdout, + &slog.HandlerOptions{ + Level: slog.LevelError, + }, + ), + ), + LogLevel: slog.LevelError, } ``` From 0ff34ecdab18d38ddadad464952233736a06e279 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 11:23:29 +0200 Subject: [PATCH 13/32] use fiber log; drop go version to 1.20 --- .github/workflows/benchmark.yml | 2 +- .github/workflows/test-nats.yml | 4 +- nats/README.md | 32 ++++++---------- nats/config.go | 51 ++++++++----------------- nats/go.mod | 4 +- nats/go.sum | 5 +++ nats/nats.go | 66 +++++++++++++++++++++------------ nats/nats_test.go | 4 +- 8 files changed, 81 insertions(+), 87 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 243b2879..060d55d6 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -86,7 +86,7 @@ jobs: uses: actions/setup-go@v5 with: # NOTE: Keep this in sync with the version from go.mod - go-version: "1.21.x" + go-version: "1.20.x" - name: Install Azurite run: | diff --git a/.github/workflows/test-nats.yml b/.github/workflows/test-nats.yml index a646ac2a..5b5dca34 100644 --- a/.github/workflows/test-nats.yml +++ b/.github/workflows/test-nats.yml @@ -8,12 +8,14 @@ on: pull_request: paths: - 'nats/**' -name: "Tests Local Storage" +name: "Tests Nats Driver" jobs: Tests: strategy: matrix: go-version: + - 1.19.x + - 1.20.x - 1.21.x runs-on: ubuntu-latest steps: diff --git a/nats/README.md b/nats/README.md index 75735344..377e3ecc 100644 --- a/nats/README.md +++ b/nats/README.md @@ -10,9 +10,9 @@ title: Nats ![Security](https://img.shields.io/github/actions/workflow/status/gofiber/storage/gosec.yml?label=Security) ![Linter](https://img.shields.io/github/actions/workflow/status/gofiber/storage/linter.yml?label=Linter) -An NATS storage driver. +An NATS Key/Value storage driver. -**Note: Requires Go 1.21 and above because of slog** +**Note: Requires Go 1.20 and above** ### Table of Contents @@ -85,17 +85,16 @@ type Config struct { ClientName string // Nats retry on failed connect: https://docs.nats.io/using-nats/developer/connecting/reconnect RetryOnFailedConnect bool - // Nats max reconnects: https://docs.nats.io/using-nats/developer/connecting/reconnect - MaxReconnects int + // Nats max reconnect attempts: https://docs.nats.io/using-nats/developer/connecting/reconnect + MaxReconnect int // Nats context Context context.Context // Nats key value config KeyValueConfig jetstream.KeyValueConfig - Logger *slog.Logger - // Applicable only if Logger is nil. - // Until go 1.22, it is weird to set log level. - // See https://github.com/golang/go/issues/62418 - LogLevel slog.Level + // Logger. Using Fiber provides the AllLogger interface for adapting the various log libraries. + Logger log.AllLogger + // Use the Logger for nats events, default: false + UseLogger bool } ``` @@ -103,20 +102,11 @@ type Config struct { ```go var ConfigDefault = Config{ - URL: nats.DefaultURL, - // RetryOnFailedConnect: true, - Context: context.Background(), + URL: nats.DefaultURL, + Context: context.Background(), + MaxReconnect: nats.DefaultMaxReconnect, KeyValueConfig: jetstream.KeyValueConfig{ Bucket: "fiber_storage", }, - Logger: slog.New( - slog.NewTextHandler( - os.Stdout, - &slog.HandlerOptions{ - Level: slog.LevelError, - }, - ), - ), - LogLevel: slog.LevelError, } ``` diff --git a/nats/config.go b/nats/config.go index 39b3a4b3..c706bd38 100644 --- a/nats/config.go +++ b/nats/config.go @@ -2,9 +2,8 @@ package nats import ( "context" - "log/slog" - "os" + "github.com/gofiber/fiber/v2/log" "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" ) @@ -23,36 +22,26 @@ type Config struct { ClientName string // Nats retry on failed connect: https://docs.nats.io/using-nats/developer/connecting/reconnect RetryOnFailedConnect bool - // Nats max reconnects: https://docs.nats.io/using-nats/developer/connecting/reconnect - MaxReconnects int + // Nats max reconnect attempts: https://docs.nats.io/using-nats/developer/connecting/reconnect + MaxReconnect int // Nats context Context context.Context // Nats key value config KeyValueConfig jetstream.KeyValueConfig - Logger *slog.Logger - // Applicable only if Logger is nil. - // Until go 1.22, it is weird to set log level. - // See https://github.com/golang/go/issues/62418 - LogLevel slog.Level + // Logger. Using Fiber provides the AllLogger interface for adapting the various log libraries. + Logger log.AllLogger + // Use the Logger for nats events, default: false + UseLogger bool } // ConfigDefault is the default config var ConfigDefault = Config{ - URL: nats.DefaultURL, - // RetryOnFailedConnect: true, - Context: context.Background(), + URL: nats.DefaultURL, + Context: context.Background(), + MaxReconnect: nats.DefaultMaxReconnect, KeyValueConfig: jetstream.KeyValueConfig{ Bucket: "fiber_storage", }, - Logger: slog.New( - slog.NewTextHandler( - os.Stdout, - &slog.HandlerOptions{ - Level: slog.LevelError, - }, - ), - ), - LogLevel: slog.LevelError, } // Helper function to set default values @@ -75,23 +64,13 @@ func configDefault(config ...Config) Config { if len(cfg.KeyValueConfig.Bucket) == 0 { cfg.KeyValueConfig.Bucket = ConfigDefault.KeyValueConfig.Bucket } - if cfg.Logger == nil { - if cfg.LogLevel != ConfigDefault.LogLevel { - cfg.Logger = slog.New( - slog.NewTextHandler( - os.Stdout, - &slog.HandlerOptions{ - Level: cfg.LogLevel, - }, - ), - ) - } else { - cfg.Logger = ConfigDefault.Logger + if cfg.UseLogger { + if cfg.Logger == nil { + cfg.Logger = log.DefaultLogger() } + } else { + cfg.Logger = nil } - // if !cfg.RetryOnFailedConnect { - // cfg.RetryOnFailedConnect = ConfigDefault.RetryOnFailedConnect - // } return cfg } diff --git a/nats/go.mod b/nats/go.mod index 2ae2d548..2b19fad0 100644 --- a/nats/go.mod +++ b/nats/go.mod @@ -1,8 +1,9 @@ module github.com/gofiber/storage/nats -go 1.21 +go 1.20 require ( + github.com/gofiber/fiber/v2 v2.52.0 github.com/nats-io/nats.go v1.32.0 github.com/stretchr/testify v1.8.4 ) @@ -13,6 +14,7 @@ require ( github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect golang.org/x/crypto v0.18.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/nats/go.sum b/nats/go.sum index 467f5854..c0bd6e5c 100644 --- a/nats/go.sum +++ b/nats/go.sum @@ -1,5 +1,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gofiber/fiber/v2 v2.52.0 h1:S+qXi7y+/Pgvqq4DrSmREGiFwtB7Bu6+QFLuIHYw/UE= +github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/nats-io/nats.go v1.32.0 h1:Bx9BZS+aXYlxW08k8Gd3yR2s73pV5XSoAQUyp1Kwvp0= @@ -12,6 +15,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= diff --git a/nats/nats.go b/nats/nats.go index 8f87dba9..fd89d9b1 100644 --- a/nats/nats.go +++ b/nats/nats.go @@ -33,14 +33,28 @@ func init() { gob.Register(entry{}) } +// logErrorw is a helper function to log error messages +func (s *Storage) logErrorw(msg string, keysAndValues ...interface{}) { + if s.cfg.UseLogger && s.cfg.Logger != nil { + s.cfg.Logger.Errorw(msg, keysAndValues...) + } +} + +// logInfow is a helper function to log error messages +func (s *Storage) logInfow(msg string, keysAndValues ...interface{}) { + if s.cfg.UseLogger && s.cfg.Logger != nil { + s.cfg.Logger.Infow(msg, keysAndValues...) + } +} + // connectHandler is a helper function to set the initial connect handler func (s *Storage) connectHandler(nc *nats.Conn) { s.mu.Lock() defer s.mu.Unlock() - s.cfg.Logger. - With("url", nc.ConnectedUrlRedacted()). - With("mod", "nats"). - Info("connected") + s.logInfow("connected", + "diver", "nats", + "url", nc.ConnectedUrlRedacted(), + ) var err error s.kv, err = newNatsKV( @@ -49,10 +63,10 @@ func (s *Storage) connectHandler(nc *nats.Conn) { s.cfg.KeyValueConfig, ) if err != nil { - s.cfg.Logger. - With("err", err). - With("mod", "nats"). - Error("kv not initialized") + s.logErrorw("kv not initialized", + "diver", "nats", + "error", err.Error(), + ) s.err = append(s.err, err) } } @@ -62,14 +76,14 @@ func (s *Storage) disconnectErrHandler(nc *nats.Conn, err error) { s.mu.Lock() defer s.mu.Unlock() if err != nil { - s.cfg.Logger. - With("err", err). - With("mod", "nats"). - Error("disconnected") + s.logErrorw("disconnected", + "diver", "nats", + "error", err.Error(), + ) } else { - s.cfg.Logger. - With("mod", "nats"). - Info("disconnected") + s.logInfow("disconnected", + "diver", "nats", + ) } nc.Opts.RetryOnFailedConnect = true if err != nil { @@ -86,11 +100,11 @@ func (s *Storage) reconnectHandler(nc *nats.Conn) { func (s *Storage) errorHandler(nc *nats.Conn, sub *nats.Subscription, err error) { s.mu.Lock() defer s.mu.Unlock() - s.cfg.Logger. - With("err", err). - With("sub", sub.Subject). - With("mod", "nats"). - Error("error") + s.logErrorw("error handler", + "diver", "nats", + "sub", sub.Subject, + "error", err.Error(), + ) if err != nil { s.err = append(s.err, fmt.Errorf("subject %q: %w", sub.Subject, err)) } @@ -100,9 +114,9 @@ func (s *Storage) errorHandler(nc *nats.Conn, sub *nats.Subscription, err error) func (s *Storage) closedHandler(nc *nats.Conn) { s.mu.RLock() defer s.mu.RUnlock() - s.cfg.Logger. - With("mod", "nats"). - Info("closed") + s.logInfow("closed", + "diver", "nats", + ) } func newNatsKV(nc *nats.Conn, ctx context.Context, keyValueConfig jetstream.KeyValueConfig) (jetstream.KeyValue, error) { @@ -152,7 +166,7 @@ func New(config ...Config) *Storage { optionalUserInfo, optionalUserCreds, nats.RetryOnFailedConnect(cfg.RetryOnFailedConnect), - nats.MaxReconnects(cfg.MaxReconnects), + nats.MaxReconnects(cfg.MaxReconnect), nats.ConnectHandler(storage.connectHandler), nats.DisconnectErrHandler(storage.disconnectErrHandler), nats.ReconnectHandler(storage.reconnectHandler), @@ -314,6 +328,10 @@ func (s *Storage) Keys() ([]string, error) { s.mu.RLock() kv := s.kv s.mu.RUnlock() + if kv == nil { + return nil, fmt.Errorf("kv not initialized: %v", s.err) + } + keyLister, err := kv.ListKeys(s.ctx) if err != nil { diff --git a/nats/nats_test.go b/nats/nats_test.go index 4f4861d5..783ad20a 100644 --- a/nats/nats_test.go +++ b/nats/nats_test.go @@ -1,7 +1,6 @@ package nats import ( - "log/slog" "testing" "time" @@ -10,8 +9,7 @@ import ( ) var config = Config{ - MaxReconnects: 1, - LogLevel: slog.LevelError, + MaxReconnect: 1, KeyValueConfig: jetstream.KeyValueConfig{ Bucket: "test", Storage: jetstream.MemoryStorage, From 65d4028a4d5d99b0dae2b73e9b66f72efa9e934c Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 11:37:07 +0200 Subject: [PATCH 14/32] Update test-nats.yml --- .github/workflows/test-nats.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test-nats.yml b/.github/workflows/test-nats.yml index 5b5dca34..abbb0e91 100644 --- a/.github/workflows/test-nats.yml +++ b/.github/workflows/test-nats.yml @@ -14,7 +14,6 @@ jobs: strategy: matrix: go-version: - - 1.19.x - 1.20.x - 1.21.x runs-on: ubuntu-latest From 4666ba0eea2afe38b5b04b3e095939ff5afe10a7 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 15:49:16 +0200 Subject: [PATCH 15/32] enable nats tls --- .github/workflows/benchmark.yml | 2 +- .github/workflows/test-nats.yml | 2 +- nats/README.md | 41 +++++++------- nats/nats_test.go | 8 ++- nats/testdata/certs/ca.pem | 27 +++++++++ nats/testdata/certs/key.pem | 28 ++++++++++ nats/testdata/certs/server.pem | 99 +++++++++++++++++++++++++++++++++ nats/testdata/nats-tls.conf | 10 ++++ 8 files changed, 194 insertions(+), 23 deletions(-) create mode 100644 nats/testdata/certs/ca.pem create mode 100644 nats/testdata/certs/key.pem create mode 100644 nats/testdata/certs/server.pem create mode 100644 nats/testdata/nats-tls.conf diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 060d55d6..1e413f92 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -135,7 +135,7 @@ jobs: - name: Run NATS run: | - docker run -d --name nats-jetstream -p 4222:4222 nats:latest --jetstream + docker run --rm -it --name nats-jetstream -p 4443:4443 -v ./nats/testdata:/testdata nats:latest --jetstream -c /testdata/nats-tls.conf sleep 2 - name: Run Benchmarks diff --git a/.github/workflows/test-nats.yml b/.github/workflows/test-nats.yml index abbb0e91..a3c4e8a8 100644 --- a/.github/workflows/test-nats.yml +++ b/.github/workflows/test-nats.yml @@ -26,7 +26,7 @@ jobs: go-version: '${{ matrix.go-version }}' - name: Run NATS run: | - docker run -d --name nats-jetstream -p 4222:4222 nats:latest --jetstream + docker run --rm -it --name nats-jetstream -p 4443:4443 -v ./nats/testdata:/testdata nats:latest --jetstream -c /testdata/nats-tls.conf sleep 2 - name: Test Nats run: cd ./nats && go test ./... -v -race diff --git a/nats/README.md b/nats/README.md index 377e3ecc..79adb7ba 100644 --- a/nats/README.md +++ b/nats/README.md @@ -31,8 +31,8 @@ func (s *Storage) Set(key string, val []byte, exp time.Duration) error func (s *Storage) Delete(key string) error func (s *Storage) Reset() error func (s *Storage) Close() error -func (s *Storage) Conn() map[string]entry -func (s *Storage) Keys() ([][]byte, error) +func (s *Storage) Conn() (*nats.Conn, jetstream.KeyValue) +func (s *Storage) Keys() ([]string, error) ``` ### Installation @@ -64,8 +64,17 @@ You can use the following possibilities to create a storage: store := nats.New() // Initialize custom config -store := nats.New(nats.Config{ - GCInterval: 10 * time.Second, +store := nats.New(Config{ + URLs: "nats://127.0.0.1:4443", + NatsOptions: []nats.Option{ + nats.MaxReconnects(2), + // Enable TLS by specifying RootCAs + nats.RootCAs("./testdata/certs/ca.pem"), + }, + KeyValueConfig: jetstream.KeyValueConfig{ + Bucket: "test", + Storage: jetstream.MemoryStorage, + }, }) ``` @@ -73,20 +82,12 @@ store := nats.New(nats.Config{ ```go type Config struct { - // Nats URL, default "nats://127.0.0.1:4222" - URL string - // Nats username - Username string - // Nats password - Password string - // Nats credentials file: https://docs.nats.io/using-nats/developer/connecting/creds - CredentialsFile string - // Nats client name + // Nats URLs, default "nats://127.0.0.1:4222". Can be comma separated list for multiple servers + URLs string + // Nats connection options. See nats_test.go for an example of how to use this. + NatsOptions []nats.Option + // Nats connection name ClientName string - // Nats retry on failed connect: https://docs.nats.io/using-nats/developer/connecting/reconnect - RetryOnFailedConnect bool - // Nats max reconnect attempts: https://docs.nats.io/using-nats/developer/connecting/reconnect - MaxReconnect int // Nats context Context context.Context // Nats key value config @@ -102,9 +103,9 @@ type Config struct { ```go var ConfigDefault = Config{ - URL: nats.DefaultURL, - Context: context.Background(), - MaxReconnect: nats.DefaultMaxReconnect, + URLs: nats.DefaultURL, + Context: context.Background(), + ClientName: "fiber_storage", KeyValueConfig: jetstream.KeyValueConfig{ Bucket: "fiber_storage", }, diff --git a/nats/nats_test.go b/nats/nats_test.go index 783ad20a..f6c30b78 100644 --- a/nats/nats_test.go +++ b/nats/nats_test.go @@ -4,12 +4,18 @@ import ( "testing" "time" + "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" "github.com/stretchr/testify/require" ) var config = Config{ - MaxReconnect: 1, + URLs: "nats://127.0.0.1:4443", + NatsOptions: []nats.Option{ + nats.MaxReconnects(2), + // Enable TLS by specifying RootCAs + nats.RootCAs("./testdata/certs/ca.pem"), + }, KeyValueConfig: jetstream.KeyValueConfig{ Bucket: "test", Storage: jetstream.MemoryStorage, diff --git a/nats/testdata/certs/ca.pem b/nats/testdata/certs/ca.pem new file mode 100644 index 00000000..911c486c --- /dev/null +++ b/nats/testdata/certs/ca.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEkDCCA3igAwIBAgIUSZwW7btc9EUbrMWtjHpbM0C2bSEwDQYJKoZIhvcNAQEL +BQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM +B1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl +IEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw +MjMwMlowcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV +BAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmlj +YXRlIEF1dGhvcml0eSAyMDIyLTA4LTI3MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAqilVqyY8rmCpTwAsLF7DEtWEq37KbljBWVjmlp2Wo6TgMd3b537t +6iO8+SbI8KH75i63RcxV3Uzt1/L9Yb6enDXF52A/U5ugmDhaa+Vsoo2HBTbCczmp +qndp7znllQqn7wNLv6aGSvaeIUeYS5Dmlh3kt7Vqbn4YRANkOUTDYGSpMv7jYKSu +1ee05Rco3H674zdwToYto8L8V7nVMrky42qZnGrJTaze+Cm9tmaIyHCwUq362CxS +dkmaEuWx11MOIFZvL80n7ci6pveDxe5MIfwMC3/oGn7mbsSqidPMcTtjw6ey5NEu +Z0UrC/2lL1FtF4gnVMKUSaEhU2oKjj0ZAQIDAQABo4IBHjCCARowHQYDVR0OBBYE +FP7Pfz4u7sSt6ltviEVsx4hIFIs6MIGuBgNVHSMEgaYwgaOAFP7Pfz4u7sSt6ltv +iEVsx4hIFIs6oXWkczBxMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5p +YTEQMA4GA1UECgwHU3luYWRpYTEQMA4GA1UECwwHbmF0cy5pbzEpMCcGA1UEAwwg +Q2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMjItMDgtMjeCFEmcFu27XPRFG6zFrYx6 +WzNAtm0hMAwGA1UdEwQFMAMBAf8wOgYJYIZIAYb4QgENBC0WK25hdHMuaW8gbmF0 +cy1zZXJ2ZXIgdGVzdC1zdWl0ZSB0cmFuc2llbnQgQ0EwDQYJKoZIhvcNAQELBQAD +ggEBAHDCHLQklYZlnzHDaSwxgGSiPUrCf2zhk2DNIYSDyBgdzrIapmaVYQRrCBtA +j/4jVFesgw5WDoe4TKsyha0QeVwJDIN8qg2pvpbmD8nOtLApfl0P966vcucxDwqO +dQWrIgNsaUdHdwdo0OfvAlTfG0v/y2X0kbL7h/el5W9kWpxM/rfbX4IHseZL2sLq +FH69SN3FhMbdIm1ldrcLBQVz8vJAGI+6B9hSSFQWljssE0JfAX+8VW/foJgMSx7A +vBTq58rLkAko56Jlzqh/4QT+ckayg9I73v1Q5/44jP1mHw35s5ZrzpDQt2sVv4l5 +lwRPJFXMwe64flUs9sM+/vqJaIY= +-----END CERTIFICATE----- diff --git a/nats/testdata/certs/key.pem b/nats/testdata/certs/key.pem new file mode 100644 index 00000000..f2c2c6c2 --- /dev/null +++ b/nats/testdata/certs/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDm+0dlzcmiLa+L +zdVqeVQ8B1/rWnErK+VvvjH7FmVodg5Z5+RXyojpd9ZBrVd6QrLSVMQPfFvBvGGX +4yI6Ph5KXUefa31vNOOMhp2FGSmaEVhETKGQ0xRh4VfaAerOP5Cunl0TbSyJyjkV +a7aeMtcqTEiFL7Ae2EtiMhTrMrYpBDQ8rzm2i1IyTb9DX5v7DUOmrSynQSlVyXCz +tRVGNL/kHlItpEku1SHt/AD3ogu8EgqQZFB8xRRw9fubYgh4Q0kx80e4k9QtTKnc +F3B2NGb/ZcE5Z+mmHIBq8J2zKMijOrdd3m5TbQmzDbETEOjs4L1eoZRLcL/cvYu5 +gmXdr4F7AgMBAAECggEBAK4sr3MiEbjcsHJAvXyzjwRRH1Bu+8VtLW7swe2vvrpd +w4aiKXrV/BXpSsRtvPgxkXyvdMSkpuBZeFI7cVTwAJFc86RQPt77x9bwr5ltFwTZ +rXCbRH3b3ZPNhByds3zhS+2Q92itu5cPyanQdn2mor9/lHPyOOGZgobCcynELL6R +wRElkeDyf5ODuWEd7ADC5IFyZuwb3azNVexIK+0yqnMmv+QzEW3hsycFmFGAeB7v +MIMjb2BhLrRr6Y5Nh+k58yM5DCf9h/OJhDpeXwLkxyK4BFg+aZffEbUX0wHDMR7f +/nMv1g6cKvDWiLU8xLzez4t2qNIBNdxw5ZSLyQRRolECgYEA+ySTKrBAqI0Uwn8H +sUFH95WhWUXryeRyGyQsnWAjZGF1+d67sSY2un2W6gfZrxRgiNLWEFq9AaUs0MuH +6syF4Xwx/aZgU/gvsGtkgzuKw1bgvekT9pS/+opmHRCZyQAFEHj0IEpzyB6rW1u/ +LdlR3ShEENnmXilFv/uF/uXP5tMCgYEA63LiT0w46aGPA/E+aLRWU10c1eZ7KdhR +c3En6zfgIxgFs8J38oLdkOR0CF6T53DSuvGR/OprVKdlnUhhDxBgT1oQjK2GlhPx +JV5uMvarJDJxAwsF+7T4H2QtZ00BtEfpyp790+TlypSG1jo/BnSMmX2uEbV722lY +hzINLY49obkCgYBEpN2YyG4T4+PtuXznxRkfogVk+kiVeVx68KtFJLbnw//UGT4i +EHjbBmLOevDT+vTb0QzzkWmh3nzeYRM4aUiatjCPzP79VJPsW54whIDMHZ32KpPr +TQMgPt3kSdpO5zN7KiRIAzGcXE2n/e7GYGUQ1uWr2XMu/4byD5SzdCscQwJ/Ymii +LoKtRvk/zWYHr7uwWSeR5dVvpQ3E/XtONAImrIRd3cRqXfJUqTrTRKxDJXkCmyBc +5FkWg0t0LUkTSDiQCJqcUDA3EINFR1kwthxja72pfpwc5Be/nV9BmuuUysVD8myB +qw8A/KsXsHKn5QrRuVXOa5hvLEXbuqYw29mX6QKBgDGDzIzpR9uPtBCqzWJmc+IJ +z4m/1NFlEz0N0QNwZ/TlhyT60ytJNcmW8qkgOSTHG7RDueEIzjQ8LKJYH7kXjfcF +6AJczUG5PQo9cdJKo9JP3e1037P/58JpLcLe8xxQ4ce03zZpzhsxR2G/tz8DstJs +b8jpnLyqfGrcV2feUtIZ +-----END PRIVATE KEY----- diff --git a/nats/testdata/certs/server.pem b/nats/testdata/certs/server.pem new file mode 100644 index 00000000..80a9d8fe --- /dev/null +++ b/nats/testdata/certs/server.pem @@ -0,0 +1,99 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 1d:d9:1f:06:dd:fd:90:26:4e:27:ea:2e:01:4b:31:e6:d2:49:31:1f + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=California, O=Synadia, OU=nats.io, CN=Certificate Authority 2022-08-27 + Validity + Not Before: Aug 27 20:23:02 2022 GMT + Not After : Aug 24 20:23:02 2032 GMT + Subject: C=US, ST=California, O=Synadia, OU=nats.io, CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:e6:fb:47:65:cd:c9:a2:2d:af:8b:cd:d5:6a:79: + 54:3c:07:5f:eb:5a:71:2b:2b:e5:6f:be:31:fb:16: + 65:68:76:0e:59:e7:e4:57:ca:88:e9:77:d6:41:ad: + 57:7a:42:b2:d2:54:c4:0f:7c:5b:c1:bc:61:97:e3: + 22:3a:3e:1e:4a:5d:47:9f:6b:7d:6f:34:e3:8c:86: + 9d:85:19:29:9a:11:58:44:4c:a1:90:d3:14:61:e1: + 57:da:01:ea:ce:3f:90:ae:9e:5d:13:6d:2c:89:ca: + 39:15:6b:b6:9e:32:d7:2a:4c:48:85:2f:b0:1e:d8: + 4b:62:32:14:eb:32:b6:29:04:34:3c:af:39:b6:8b: + 52:32:4d:bf:43:5f:9b:fb:0d:43:a6:ad:2c:a7:41: + 29:55:c9:70:b3:b5:15:46:34:bf:e4:1e:52:2d:a4: + 49:2e:d5:21:ed:fc:00:f7:a2:0b:bc:12:0a:90:64: + 50:7c:c5:14:70:f5:fb:9b:62:08:78:43:49:31:f3: + 47:b8:93:d4:2d:4c:a9:dc:17:70:76:34:66:ff:65: + c1:39:67:e9:a6:1c:80:6a:f0:9d:b3:28:c8:a3:3a: + b7:5d:de:6e:53:6d:09:b3:0d:b1:13:10:e8:ec:e0: + bd:5e:a1:94:4b:70:bf:dc:bd:8b:b9:82:65:dd:af: + 81:7b + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + nats.io nats-server test-suite certificate + X509v3 Subject Key Identifier: + 2B:8C:A3:8B:DB:DB:5C:CE:18:DB:F6:A8:31:4E:C2:3E:EE:D3:40:7E + X509v3 Authority Key Identifier: + keyid:FE:CF:7F:3E:2E:EE:C4:AD:EA:5B:6F:88:45:6C:C7:88:48:14:8B:3A + DirName:/C=US/ST=California/O=Synadia/OU=nats.io/CN=Certificate Authority 2022-08-27 + serial:49:9C:16:ED:BB:5C:F4:45:1B:AC:C5:AD:8C:7A:5B:33:40:B6:6D:21 + + X509v3 Subject Alternative Name: + DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1 + Netscape Cert Type: + SSL Client, SSL Server + X509v3 Key Usage: + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, Netscape Server Gated Crypto, Microsoft Server Gated Crypto, TLS Web Client Authentication + Signature Algorithm: sha256WithRSAEncryption + 54:49:34:2b:38:d1:aa:3b:43:60:4c:3f:6a:f8:74:ca:49:53: + a1:af:12:d3:a8:17:90:7b:9d:a3:69:13:6e:da:2c:b7:61:31: + ac:eb:00:93:92:fc:0c:10:d4:18:a0:16:61:94:4b:42:cb:eb: + 7a:f6:80:c6:45:c0:9c:09:aa:a9:48:e8:36:e3:c5:be:36:e0: + e9:78:2a:bb:ab:64:9b:20:eb:e6:0f:63:2b:59:c3:58:0b:3a: + 84:15:04:c1:7e:12:03:1b:09:25:8d:4c:03:e8:18:26:c0:6c: + b7:90:b1:fd:bc:f1:cf:d0:d5:4a:03:15:71:0c:7d:c1:76:87: + 92:f1:3e:bc:75:51:5a:c4:36:a4:ff:91:98:df:33:5d:a7:38: + de:50:29:fd:0f:c8:55:e6:8f:24:c2:2e:98:ab:d9:5d:65:2f: + 50:cc:25:f6:84:f2:21:2e:5e:76:d0:86:1e:69:8b:cb:8a:3a: + 2d:79:21:5e:e7:f7:2d:06:18:a1:13:cb:01:c3:46:91:2a:de: + b4:82:d7:c3:62:6f:08:a1:d5:90:19:30:9d:64:8e:e4:f8:ba: + 4f:2f:ba:13:b4:a3:9f:d1:d5:77:64:8a:3e:eb:53:c5:47:ac: + ab:3e:0e:7a:9b:a6:f4:48:25:66:eb:c7:4c:f9:50:24:eb:71: + e0:75:ae:e6 +-----BEGIN CERTIFICATE----- +MIIE+TCCA+GgAwIBAgIUHdkfBt39kCZOJ+ouAUsx5tJJMR8wDQYJKoZIhvcNAQEL +BQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM +B1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl +IEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw +MjMwMlowWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV +BAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xEjAQBgNVBAMMCWxvY2FsaG9z +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOb7R2XNyaItr4vN1Wp5 +VDwHX+tacSsr5W++MfsWZWh2Dlnn5FfKiOl31kGtV3pCstJUxA98W8G8YZfjIjo+ +HkpdR59rfW8044yGnYUZKZoRWERMoZDTFGHhV9oB6s4/kK6eXRNtLInKORVrtp4y +1ypMSIUvsB7YS2IyFOsytikENDyvObaLUjJNv0Nfm/sNQ6atLKdBKVXJcLO1FUY0 +v+QeUi2kSS7VIe38APeiC7wSCpBkUHzFFHD1+5tiCHhDSTHzR7iT1C1MqdwXcHY0 +Zv9lwTln6aYcgGrwnbMoyKM6t13eblNtCbMNsRMQ6OzgvV6hlEtwv9y9i7mCZd2v +gXsCAwEAAaOCAZ4wggGaMAkGA1UdEwQCMAAwOQYJYIZIAYb4QgENBCwWKm5hdHMu +aW8gbmF0cy1zZXJ2ZXIgdGVzdC1zdWl0ZSBjZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU +K4yji9vbXM4Y2/aoMU7CPu7TQH4wga4GA1UdIwSBpjCBo4AU/s9/Pi7uxK3qW2+I +RWzHiEgUizqhdaRzMHExCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh +MRAwDgYDVQQKDAdTeW5hZGlhMRAwDgYDVQQLDAduYXRzLmlvMSkwJwYDVQQDDCBD +ZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAyMi0wOC0yN4IUSZwW7btc9EUbrMWtjHpb +M0C2bSEwLAYDVR0RBCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAA +AAABMBEGCWCGSAGG+EIBAQQEAwIGwDALBgNVHQ8EBAMCBaAwNAYDVR0lBC0wKwYI +KwYBBQUHAwEGCWCGSAGG+EIEAQYKKwYBBAGCNwoDAwYIKwYBBQUHAwIwDQYJKoZI +hvcNAQELBQADggEBAFRJNCs40ao7Q2BMP2r4dMpJU6GvEtOoF5B7naNpE27aLLdh +MazrAJOS/AwQ1BigFmGUS0LL63r2gMZFwJwJqqlI6Dbjxb424Ol4KrurZJsg6+YP +YytZw1gLOoQVBMF+EgMbCSWNTAPoGCbAbLeQsf288c/Q1UoDFXEMfcF2h5LxPrx1 +UVrENqT/kZjfM12nON5QKf0PyFXmjyTCLpir2V1lL1DMJfaE8iEuXnbQhh5pi8uK +Oi15IV7n9y0GGKETywHDRpEq3rSC18Nibwih1ZAZMJ1kjuT4uk8vuhO0o5/R1Xdk +ij7rU8VHrKs+DnqbpvRIJWbrx0z5UCTrceB1ruY= +-----END CERTIFICATE----- diff --git a/nats/testdata/nats-tls.conf b/nats/testdata/nats-tls.conf new file mode 100644 index 00000000..118515f3 --- /dev/null +++ b/nats/testdata/nats-tls.conf @@ -0,0 +1,10 @@ +# Simple TLS config file + +port: 4443 +net: 0.0.0.0 # net interface + +tls { + cert_file: "./testdata/certs/server.pem" + key_file: "./testdata/certs/key.pem" + timeout: 2 +} From f6a0831fab1aae87883cc50079946872eca4eeb6 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 15:49:46 +0200 Subject: [PATCH 16/32] refactor nats options to accomodate any/all of them --- nats/config.go | 31 +++++++++++-------------- nats/nats.go | 63 +++++++++++++++++++++++++++++++------------------- 2 files changed, 52 insertions(+), 42 deletions(-) diff --git a/nats/config.go b/nats/config.go index c706bd38..f78c77e6 100644 --- a/nats/config.go +++ b/nats/config.go @@ -10,20 +10,12 @@ import ( // Config defines the config for storage. type Config struct { - // Nats URL, default "nats://127.0.0.1:4222" - URL string - // Nats username - Username string - // Nats password - Password string - // Nats credentials file: https://docs.nats.io/using-nats/developer/connecting/creds - CredentialsFile string - // Nats client name + // Nats URLs, default "nats://127.0.0.1:4222". Can be comma separated list for multiple servers + URLs string + // Nats connection options. See nats_test.go for an example of how to use this. + NatsOptions []nats.Option + // Nats connection name ClientName string - // Nats retry on failed connect: https://docs.nats.io/using-nats/developer/connecting/reconnect - RetryOnFailedConnect bool - // Nats max reconnect attempts: https://docs.nats.io/using-nats/developer/connecting/reconnect - MaxReconnect int // Nats context Context context.Context // Nats key value config @@ -36,9 +28,9 @@ type Config struct { // ConfigDefault is the default config var ConfigDefault = Config{ - URL: nats.DefaultURL, - Context: context.Background(), - MaxReconnect: nats.DefaultMaxReconnect, + URLs: nats.DefaultURL, + Context: context.Background(), + ClientName: "fiber_storage", KeyValueConfig: jetstream.KeyValueConfig{ Bucket: "fiber_storage", }, @@ -55,8 +47,8 @@ func configDefault(config ...Config) Config { cfg := config[0] // Set default values - if cfg.URL == "" { - cfg.URL = ConfigDefault.URL + if cfg.URLs == "" { + cfg.URLs = ConfigDefault.URLs } if cfg.Context == nil { cfg.Context = ConfigDefault.Context @@ -71,6 +63,9 @@ func configDefault(config ...Config) Config { } else { cfg.Logger = nil } + if cfg.ClientName == "" { + cfg.ClientName = ConfigDefault.ClientName + } return cfg } diff --git a/nats/nats.go b/nats/nats.go index fd89d9b1..430966f3 100644 --- a/nats/nats.go +++ b/nats/nats.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "net" + "strings" "sync" "time" @@ -139,6 +140,21 @@ func newNatsKV(nc *nats.Conn, ctx context.Context, keyValueConfig jetstream.KeyV return jskv, nil } +// Process the url string argument to Connect. +// Return an array of urls, even if only one. +func processUrlString(url string) []string { + urls := strings.Split(url, ",") + var j int + for _, s := range urls { + u := strings.TrimSpace(s) + if len(u) > 0 { + urls[j] = u + j++ + } + } + return urls[:j] +} + // New creates a new nats kv storage func New(config ...Config) *Storage { // Set default config @@ -149,32 +165,31 @@ func New(config ...Config) *Storage { cfg: cfg, } - var optionalUserCreds nats.Option - if len(cfg.CredentialsFile) > 0 { - optionalUserCreds = nats.UserCredentials(cfg.CredentialsFile) - } - var optionalUserInfo nats.Option - if len(cfg.Username) > 0 { - optionalUserInfo = nats.UserInfo(cfg.Username, cfg.Password) + // Set the nats options with default custom handlers + cfg.NatsOptions = append( + []nats.Option{ + nats.ConnectHandler(storage.connectHandler), + nats.DisconnectErrHandler(storage.disconnectErrHandler), + nats.ReconnectHandler(storage.reconnectHandler), + nats.ErrorHandler(storage.errorHandler), + nats.ClosedHandler(storage.closedHandler), + }, + cfg.NatsOptions..., + ) + natsOpts := nats.GetDefaultOptions() + natsOpts.Servers = processUrlString(cfg.URLs) + for _, opt := range cfg.NatsOptions { + if opt != nil { + if err := opt(&natsOpts); err != nil { + panic(err) + } + } } - - // Connect to NATS with minimal options + // Connect to NATS var err error - storage.nc, err = nats.Connect( - cfg.URL, - nats.Name(cfg.ClientName), - optionalUserInfo, - optionalUserCreds, - nats.RetryOnFailedConnect(cfg.RetryOnFailedConnect), - nats.MaxReconnects(cfg.MaxReconnect), - nats.ConnectHandler(storage.connectHandler), - nats.DisconnectErrHandler(storage.disconnectErrHandler), - nats.ReconnectHandler(storage.reconnectHandler), - nats.ErrorHandler(storage.errorHandler), - nats.ClosedHandler(storage.closedHandler), - ) + storage.nc, err = natsOpts.Connect() - if opErr, ok := err.(*net.OpError); ok && cfg.RetryOnFailedConnect { + if opErr, ok := err.(*net.OpError); ok && natsOpts.RetryOnFailedConnect { if opErr.Op != "dial" { panic(err) } @@ -293,12 +308,12 @@ func (s *Storage) Reset() error { // Create the bucket s.mu.Lock() + defer s.mu.Unlock() s.kv, err = newNatsKV( s.nc, s.ctx, s.cfg.KeyValueConfig, ) - s.mu.Unlock() if err != nil { s.err = []error{err} return err From 0d3ad13763d705433c7a97dc8d70fde7ed10dadb Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 15:53:29 +0200 Subject: [PATCH 17/32] run container in background --- .github/workflows/benchmark.yml | 2 +- .github/workflows/test-nats.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 1e413f92..08c9af58 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -135,7 +135,7 @@ jobs: - name: Run NATS run: | - docker run --rm -it --name nats-jetstream -p 4443:4443 -v ./nats/testdata:/testdata nats:latest --jetstream -c /testdata/nats-tls.conf + docker run -d --name nats-jetstream -p 4443:4443 -v ./nats/testdata:/testdata nats:latest --jetstream -c /testdata/nats-tls.conf sleep 2 - name: Run Benchmarks diff --git a/.github/workflows/test-nats.yml b/.github/workflows/test-nats.yml index a3c4e8a8..8e04e984 100644 --- a/.github/workflows/test-nats.yml +++ b/.github/workflows/test-nats.yml @@ -26,7 +26,7 @@ jobs: go-version: '${{ matrix.go-version }}' - name: Run NATS run: | - docker run --rm -it --name nats-jetstream -p 4443:4443 -v ./nats/testdata:/testdata nats:latest --jetstream -c /testdata/nats-tls.conf + docker run -d --name nats-jetstream -p 4443:4443 -v ./nats/testdata:/testdata nats:latest --jetstream -c /testdata/nats-tls.conf sleep 2 - name: Test Nats run: cd ./nats && go test ./... -v -race From c8ff8031274bde60bc76bb2ac5cc319762a3b6e1 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 16:12:41 +0200 Subject: [PATCH 18/32] Update gosec.yml --- .github/workflows/gosec.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/gosec.yml b/.github/workflows/gosec.yml index 2520ecd4..6db5253a 100644 --- a/.github/workflows/gosec.yml +++ b/.github/workflows/gosec.yml @@ -38,6 +38,7 @@ jobs: json: true escape_json: false dir_names: true + dir_names_max_depth: '1' dir_names_exclude_current_dir: true gosec-scan: From 55a3cd018e147beb3765a40bd4f3ad63a7218726 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 16:40:25 +0200 Subject: [PATCH 19/32] Update README.md --- nats/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nats/README.md b/nats/README.md index 79adb7ba..63a9fdfb 100644 --- a/nats/README.md +++ b/nats/README.md @@ -10,7 +10,7 @@ title: Nats ![Security](https://img.shields.io/github/actions/workflow/status/gofiber/storage/gosec.yml?label=Security) ![Linter](https://img.shields.io/github/actions/workflow/status/gofiber/storage/linter.yml?label=Linter) -An NATS Key/Value storage driver. +A NATS Key/Value storage driver. **Note: Requires Go 1.20 and above** @@ -37,7 +37,7 @@ func (s *Storage) Keys() ([]string, error) ### Installation -[NATS KV](https://docs.nats.io/nats-concepts/jetstream/key-value-store) driver is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: +[NATS Key/Value Store](https://docs.nats.io/nats-concepts/jetstream/key-value-store) driver is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet: ```bash go mod init github.com// From 59646e5027f4a3065262cf1a1ce9a791318eab90 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 16:51:18 +0200 Subject: [PATCH 20/32] remove redundant check --- nats/nats.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nats/nats.go b/nats/nats.go index 430966f3..104e6ed2 100644 --- a/nats/nats.go +++ b/nats/nats.go @@ -36,14 +36,14 @@ func init() { // logErrorw is a helper function to log error messages func (s *Storage) logErrorw(msg string, keysAndValues ...interface{}) { - if s.cfg.UseLogger && s.cfg.Logger != nil { + if s.cfg.Logger != nil { s.cfg.Logger.Errorw(msg, keysAndValues...) } } // logInfow is a helper function to log error messages func (s *Storage) logInfow(msg string, keysAndValues ...interface{}) { - if s.cfg.UseLogger && s.cfg.Logger != nil { + if s.cfg.Logger != nil { s.cfg.Logger.Infow(msg, keysAndValues...) } } From 9e30af648efbd9aaf8063f8df576a3f3bd419072 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 16:56:21 +0200 Subject: [PATCH 21/32] refactor verbosity --- nats/README.md | 2 +- nats/config.go | 4 ++-- nats/nats.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nats/README.md b/nats/README.md index 63a9fdfb..f654e06a 100644 --- a/nats/README.md +++ b/nats/README.md @@ -95,7 +95,7 @@ type Config struct { // Logger. Using Fiber provides the AllLogger interface for adapting the various log libraries. Logger log.AllLogger // Use the Logger for nats events, default: false - UseLogger bool + Verbose bool } ``` diff --git a/nats/config.go b/nats/config.go index f78c77e6..406bff07 100644 --- a/nats/config.go +++ b/nats/config.go @@ -23,7 +23,7 @@ type Config struct { // Logger. Using Fiber provides the AllLogger interface for adapting the various log libraries. Logger log.AllLogger // Use the Logger for nats events, default: false - UseLogger bool + Verbose bool } // ConfigDefault is the default config @@ -56,7 +56,7 @@ func configDefault(config ...Config) Config { if len(cfg.KeyValueConfig.Bucket) == 0 { cfg.KeyValueConfig.Bucket = ConfigDefault.KeyValueConfig.Bucket } - if cfg.UseLogger { + if cfg.Verbose { if cfg.Logger == nil { cfg.Logger = log.DefaultLogger() } diff --git a/nats/nats.go b/nats/nats.go index 104e6ed2..692f8fe1 100644 --- a/nats/nats.go +++ b/nats/nats.go @@ -36,14 +36,14 @@ func init() { // logErrorw is a helper function to log error messages func (s *Storage) logErrorw(msg string, keysAndValues ...interface{}) { - if s.cfg.Logger != nil { + if s.cfg.Verbose { s.cfg.Logger.Errorw(msg, keysAndValues...) } } // logInfow is a helper function to log error messages func (s *Storage) logInfow(msg string, keysAndValues ...interface{}) { - if s.cfg.Logger != nil { + if s.cfg.Verbose { s.cfg.Logger.Infow(msg, keysAndValues...) } } From 64dc43c35a4a93fb7350d01dbad98ddfc5b62b65 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 17:04:55 +0200 Subject: [PATCH 22/32] improve locking --- nats/nats.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/nats/nats.go b/nats/nats.go index 692f8fe1..897a34fa 100644 --- a/nats/nats.go +++ b/nats/nats.go @@ -50,14 +50,14 @@ func (s *Storage) logInfow(msg string, keysAndValues ...interface{}) { // connectHandler is a helper function to set the initial connect handler func (s *Storage) connectHandler(nc *nats.Conn) { - s.mu.Lock() - defer s.mu.Unlock() s.logInfow("connected", "diver", "nats", "url", nc.ConnectedUrlRedacted(), ) var err error + s.mu.Lock() + defer s.mu.Unlock() s.kv, err = newNatsKV( nc, s.ctx, @@ -74,8 +74,6 @@ func (s *Storage) connectHandler(nc *nats.Conn) { // disconnectErrHandler is a helper function to set the disconnect error handler func (s *Storage) disconnectErrHandler(nc *nats.Conn, err error) { - s.mu.Lock() - defer s.mu.Unlock() if err != nil { s.logErrorw("disconnected", "diver", "nats", @@ -86,6 +84,8 @@ func (s *Storage) disconnectErrHandler(nc *nats.Conn, err error) { "diver", "nats", ) } + s.mu.Lock() + defer s.mu.Unlock() nc.Opts.RetryOnFailedConnect = true if err != nil { s.err = append(s.err, err) @@ -99,13 +99,13 @@ func (s *Storage) reconnectHandler(nc *nats.Conn) { // errorHandler is a helper function to set the error handler func (s *Storage) errorHandler(nc *nats.Conn, sub *nats.Subscription, err error) { - s.mu.Lock() - defer s.mu.Unlock() s.logErrorw("error handler", "diver", "nats", "sub", sub.Subject, "error", err.Error(), ) + s.mu.Lock() + defer s.mu.Unlock() if err != nil { s.err = append(s.err, fmt.Errorf("subject %q: %w", sub.Subject, err)) } @@ -113,8 +113,6 @@ func (s *Storage) errorHandler(nc *nats.Conn, sub *nats.Subscription, err error) // closedHandler is a helper function to set the closed handler func (s *Storage) closedHandler(nc *nats.Conn) { - s.mu.RLock() - defer s.mu.RUnlock() s.logInfow("closed", "diver", "nats", ) From 999511813e2ad314fd671437bc991f570f9967c9 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 17:10:34 +0200 Subject: [PATCH 23/32] add WaitForConnection conf --- nats/README.md | 3 +++ nats/config.go | 7 +++++++ nats/nats.go | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/nats/README.md b/nats/README.md index f654e06a..ae46f123 100644 --- a/nats/README.md +++ b/nats/README.md @@ -96,6 +96,8 @@ type Config struct { Logger log.AllLogger // Use the Logger for nats events, default: false Verbose bool + // Wait for connection to be established, default: 100ms + WaitForConnection time.Duration } ``` @@ -109,5 +111,6 @@ var ConfigDefault = Config{ KeyValueConfig: jetstream.KeyValueConfig{ Bucket: "fiber_storage", }, + WaitForConnection: 100 * time.Millisecond, } ``` diff --git a/nats/config.go b/nats/config.go index 406bff07..4a19e67d 100644 --- a/nats/config.go +++ b/nats/config.go @@ -2,6 +2,7 @@ package nats import ( "context" + "time" "github.com/gofiber/fiber/v2/log" "github.com/nats-io/nats.go" @@ -24,6 +25,8 @@ type Config struct { Logger log.AllLogger // Use the Logger for nats events, default: false Verbose bool + // Wait for connection to be established, default: 100ms + WaitForConnection time.Duration } // ConfigDefault is the default config @@ -34,6 +37,7 @@ var ConfigDefault = Config{ KeyValueConfig: jetstream.KeyValueConfig{ Bucket: "fiber_storage", }, + WaitForConnection: 100 * time.Millisecond, } // Helper function to set default values @@ -66,6 +70,9 @@ func configDefault(config ...Config) Config { if cfg.ClientName == "" { cfg.ClientName = ConfigDefault.ClientName } + if cfg.WaitForConnection == 0 { + cfg.WaitForConnection = ConfigDefault.WaitForConnection + } return cfg } diff --git a/nats/nats.go b/nats/nats.go index 897a34fa..9f850538 100644 --- a/nats/nats.go +++ b/nats/nats.go @@ -196,7 +196,7 @@ func New(config ...Config) *Storage { } // TODO improve this crude way to wait for the connection to be established - time.Sleep(100 * time.Millisecond) + time.Sleep(cfg.WaitForConnection) return storage } From 520e6eecd1c73337f15f83d740390182abebf0ec Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 30 Jan 2024 17:13:25 +0200 Subject: [PATCH 24/32] Update config.go and README.md --- nats/README.md | 2 +- nats/config.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nats/README.md b/nats/README.md index ae46f123..8955fd56 100644 --- a/nats/README.md +++ b/nats/README.md @@ -92,7 +92,7 @@ type Config struct { Context context.Context // Nats key value config KeyValueConfig jetstream.KeyValueConfig - // Logger. Using Fiber provides the AllLogger interface for adapting the various log libraries. + // Logger. Using Fiber AllLogger interface for adapting the various log libraries. Logger log.AllLogger // Use the Logger for nats events, default: false Verbose bool diff --git a/nats/config.go b/nats/config.go index 4a19e67d..7f43c79e 100644 --- a/nats/config.go +++ b/nats/config.go @@ -21,7 +21,7 @@ type Config struct { Context context.Context // Nats key value config KeyValueConfig jetstream.KeyValueConfig - // Logger. Using Fiber provides the AllLogger interface for adapting the various log libraries. + // Logger. Using Fiber AllLogger interface for adapting the various log libraries. Logger log.AllLogger // Use the Logger for nats events, default: false Verbose bool From fa7ea2335f5b60c2c6cc62b7098aa09db00dc6d7 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Mon, 5 Feb 2024 12:24:21 +0200 Subject: [PATCH 25/32] Improve errors --- nats/nats.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nats/nats.go b/nats/nats.go index 9f850538..4d8c3a60 100644 --- a/nats/nats.go +++ b/nats/nats.go @@ -19,7 +19,7 @@ import ( type Storage struct { nc *nats.Conn kv jetstream.KeyValue - err []error + err error ctx context.Context cfg Config mu sync.RWMutex @@ -68,7 +68,7 @@ func (s *Storage) connectHandler(nc *nats.Conn) { "diver", "nats", "error", err.Error(), ) - s.err = append(s.err, err) + s.err = errors.Join(s.err, err) } } @@ -88,7 +88,7 @@ func (s *Storage) disconnectErrHandler(nc *nats.Conn, err error) { defer s.mu.Unlock() nc.Opts.RetryOnFailedConnect = true if err != nil { - s.err = append(s.err, err) + s.err = errors.Join(s.err, err) } } @@ -107,7 +107,7 @@ func (s *Storage) errorHandler(nc *nats.Conn, sub *nats.Subscription, err error) s.mu.Lock() defer s.mu.Unlock() if err != nil { - s.err = append(s.err, fmt.Errorf("subject %q: %w", sub.Subject, err)) + s.err = errors.Join(s.err, fmt.Errorf("subject %q: %w", sub.Subject, err)) } } @@ -313,7 +313,7 @@ func (s *Storage) Reset() error { s.cfg.KeyValueConfig, ) if err != nil { - s.err = []error{err} + s.err = errors.Join(err) return err } From d30f343489c28289c9ff31ab085b448699352993 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Mon, 5 Feb 2024 13:18:41 +0200 Subject: [PATCH 26/32] Update root README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e35501c..acd21c1b 100644 --- a/README.md +++ b/README.md @@ -71,4 +71,4 @@ type Storage interface { - [S3](./s3/README.md) - [ScyllaDB](./scylladb/README.md) - [SQLite3](./sqlite3/README.md) - +- [NATS Key/Value Store](./nats/README.md) From cec55d37c4adf9915ff2997713ac64667a784d3a Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Mon, 5 Feb 2024 13:39:56 +0200 Subject: [PATCH 27/32] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index acd21c1b..573df7fc 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ type Storage interface { - [MongoDB](./mongodb/README.md) - [MSSQL](./mssql/README.md) - [MySQL](./mysql/README.md) +- [NATS](./nats/README.md) - [Pebble](./pebble/README.md) - [Postgres](./postgres/README.md) - [Redis](./redis/README.md) @@ -71,4 +72,3 @@ type Storage interface { - [S3](./s3/README.md) - [ScyllaDB](./scylladb/README.md) - [SQLite3](./sqlite3/README.md) -- [NATS Key/Value Store](./nats/README.md) From f2bbb2b75cc8c6f7d95845be5945f828c969ff6d Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Mon, 5 Feb 2024 15:03:15 +0200 Subject: [PATCH 28/32] using ./.github/scripts/gen-test-certs.sh --- .github/workflows/benchmark.yml | 3 +- .github/workflows/test-nats.yml | 4 +- nats/nats_test.go | 2 +- nats/testdata/certs/ca.pem | 27 --------- nats/testdata/certs/key.pem | 28 ---------- nats/testdata/certs/server.pem | 99 --------------------------------- nats/testdata/nats-tls.conf | 4 +- 7 files changed, 8 insertions(+), 159 deletions(-) delete mode 100644 nats/testdata/certs/ca.pem delete mode 100644 nats/testdata/certs/key.pem delete mode 100644 nats/testdata/certs/server.pem diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 4e5c709c..23500b70 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -135,7 +135,8 @@ jobs: - name: Run NATS run: | - docker run -d --name nats-jetstream -p 4443:4443 -v ./nats/testdata:/testdata nats:latest --jetstream -c /testdata/nats-tls.conf + ./.github/scripts/gen-test-certs.sh + docker run -d --name nats-jetstream -p 4443:4443 -v ./nats/testdata:/testdata -v ./tls:/tls nats:latest --jetstream -c /testdata/nats-tls.conf sleep 2 - name: Run Benchmarks diff --git a/.github/workflows/test-nats.yml b/.github/workflows/test-nats.yml index 8e04e984..1712bf9a 100644 --- a/.github/workflows/test-nats.yml +++ b/.github/workflows/test-nats.yml @@ -24,9 +24,11 @@ jobs: uses: actions/setup-go@v5 with: go-version: '${{ matrix.go-version }}' + - name: Generate config + run: ./.github/scripts/gen-test-certs.sh - name: Run NATS run: | - docker run -d --name nats-jetstream -p 4443:4443 -v ./nats/testdata:/testdata nats:latest --jetstream -c /testdata/nats-tls.conf + docker run -d --name nats-jetstream -p 4443:4443 -v ./nats/testdata:/testdata -v ./tls:/tls nats:latest --jetstream -c /testdata/nats-tls.conf sleep 2 - name: Test Nats run: cd ./nats && go test ./... -v -race diff --git a/nats/nats_test.go b/nats/nats_test.go index f6c30b78..ea6fe324 100644 --- a/nats/nats_test.go +++ b/nats/nats_test.go @@ -14,7 +14,7 @@ var config = Config{ NatsOptions: []nats.Option{ nats.MaxReconnects(2), // Enable TLS by specifying RootCAs - nats.RootCAs("./testdata/certs/ca.pem"), + nats.RootCAs("./tls/ca.crt"), }, KeyValueConfig: jetstream.KeyValueConfig{ Bucket: "test", diff --git a/nats/testdata/certs/ca.pem b/nats/testdata/certs/ca.pem deleted file mode 100644 index 911c486c..00000000 --- a/nats/testdata/certs/ca.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEkDCCA3igAwIBAgIUSZwW7btc9EUbrMWtjHpbM0C2bSEwDQYJKoZIhvcNAQEL -BQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM -B1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl -IEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw -MjMwMlowcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV -BAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmlj -YXRlIEF1dGhvcml0eSAyMDIyLTA4LTI3MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAqilVqyY8rmCpTwAsLF7DEtWEq37KbljBWVjmlp2Wo6TgMd3b537t -6iO8+SbI8KH75i63RcxV3Uzt1/L9Yb6enDXF52A/U5ugmDhaa+Vsoo2HBTbCczmp -qndp7znllQqn7wNLv6aGSvaeIUeYS5Dmlh3kt7Vqbn4YRANkOUTDYGSpMv7jYKSu -1ee05Rco3H674zdwToYto8L8V7nVMrky42qZnGrJTaze+Cm9tmaIyHCwUq362CxS -dkmaEuWx11MOIFZvL80n7ci6pveDxe5MIfwMC3/oGn7mbsSqidPMcTtjw6ey5NEu -Z0UrC/2lL1FtF4gnVMKUSaEhU2oKjj0ZAQIDAQABo4IBHjCCARowHQYDVR0OBBYE -FP7Pfz4u7sSt6ltviEVsx4hIFIs6MIGuBgNVHSMEgaYwgaOAFP7Pfz4u7sSt6ltv -iEVsx4hIFIs6oXWkczBxMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5p -YTEQMA4GA1UECgwHU3luYWRpYTEQMA4GA1UECwwHbmF0cy5pbzEpMCcGA1UEAwwg -Q2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMjItMDgtMjeCFEmcFu27XPRFG6zFrYx6 -WzNAtm0hMAwGA1UdEwQFMAMBAf8wOgYJYIZIAYb4QgENBC0WK25hdHMuaW8gbmF0 -cy1zZXJ2ZXIgdGVzdC1zdWl0ZSB0cmFuc2llbnQgQ0EwDQYJKoZIhvcNAQELBQAD -ggEBAHDCHLQklYZlnzHDaSwxgGSiPUrCf2zhk2DNIYSDyBgdzrIapmaVYQRrCBtA -j/4jVFesgw5WDoe4TKsyha0QeVwJDIN8qg2pvpbmD8nOtLApfl0P966vcucxDwqO -dQWrIgNsaUdHdwdo0OfvAlTfG0v/y2X0kbL7h/el5W9kWpxM/rfbX4IHseZL2sLq -FH69SN3FhMbdIm1ldrcLBQVz8vJAGI+6B9hSSFQWljssE0JfAX+8VW/foJgMSx7A -vBTq58rLkAko56Jlzqh/4QT+ckayg9I73v1Q5/44jP1mHw35s5ZrzpDQt2sVv4l5 -lwRPJFXMwe64flUs9sM+/vqJaIY= ------END CERTIFICATE----- diff --git a/nats/testdata/certs/key.pem b/nats/testdata/certs/key.pem deleted file mode 100644 index f2c2c6c2..00000000 --- a/nats/testdata/certs/key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDm+0dlzcmiLa+L -zdVqeVQ8B1/rWnErK+VvvjH7FmVodg5Z5+RXyojpd9ZBrVd6QrLSVMQPfFvBvGGX -4yI6Ph5KXUefa31vNOOMhp2FGSmaEVhETKGQ0xRh4VfaAerOP5Cunl0TbSyJyjkV -a7aeMtcqTEiFL7Ae2EtiMhTrMrYpBDQ8rzm2i1IyTb9DX5v7DUOmrSynQSlVyXCz -tRVGNL/kHlItpEku1SHt/AD3ogu8EgqQZFB8xRRw9fubYgh4Q0kx80e4k9QtTKnc -F3B2NGb/ZcE5Z+mmHIBq8J2zKMijOrdd3m5TbQmzDbETEOjs4L1eoZRLcL/cvYu5 -gmXdr4F7AgMBAAECggEBAK4sr3MiEbjcsHJAvXyzjwRRH1Bu+8VtLW7swe2vvrpd -w4aiKXrV/BXpSsRtvPgxkXyvdMSkpuBZeFI7cVTwAJFc86RQPt77x9bwr5ltFwTZ -rXCbRH3b3ZPNhByds3zhS+2Q92itu5cPyanQdn2mor9/lHPyOOGZgobCcynELL6R -wRElkeDyf5ODuWEd7ADC5IFyZuwb3azNVexIK+0yqnMmv+QzEW3hsycFmFGAeB7v -MIMjb2BhLrRr6Y5Nh+k58yM5DCf9h/OJhDpeXwLkxyK4BFg+aZffEbUX0wHDMR7f -/nMv1g6cKvDWiLU8xLzez4t2qNIBNdxw5ZSLyQRRolECgYEA+ySTKrBAqI0Uwn8H -sUFH95WhWUXryeRyGyQsnWAjZGF1+d67sSY2un2W6gfZrxRgiNLWEFq9AaUs0MuH -6syF4Xwx/aZgU/gvsGtkgzuKw1bgvekT9pS/+opmHRCZyQAFEHj0IEpzyB6rW1u/ -LdlR3ShEENnmXilFv/uF/uXP5tMCgYEA63LiT0w46aGPA/E+aLRWU10c1eZ7KdhR -c3En6zfgIxgFs8J38oLdkOR0CF6T53DSuvGR/OprVKdlnUhhDxBgT1oQjK2GlhPx -JV5uMvarJDJxAwsF+7T4H2QtZ00BtEfpyp790+TlypSG1jo/BnSMmX2uEbV722lY -hzINLY49obkCgYBEpN2YyG4T4+PtuXznxRkfogVk+kiVeVx68KtFJLbnw//UGT4i -EHjbBmLOevDT+vTb0QzzkWmh3nzeYRM4aUiatjCPzP79VJPsW54whIDMHZ32KpPr -TQMgPt3kSdpO5zN7KiRIAzGcXE2n/e7GYGUQ1uWr2XMu/4byD5SzdCscQwJ/Ymii -LoKtRvk/zWYHr7uwWSeR5dVvpQ3E/XtONAImrIRd3cRqXfJUqTrTRKxDJXkCmyBc -5FkWg0t0LUkTSDiQCJqcUDA3EINFR1kwthxja72pfpwc5Be/nV9BmuuUysVD8myB -qw8A/KsXsHKn5QrRuVXOa5hvLEXbuqYw29mX6QKBgDGDzIzpR9uPtBCqzWJmc+IJ -z4m/1NFlEz0N0QNwZ/TlhyT60ytJNcmW8qkgOSTHG7RDueEIzjQ8LKJYH7kXjfcF -6AJczUG5PQo9cdJKo9JP3e1037P/58JpLcLe8xxQ4ce03zZpzhsxR2G/tz8DstJs -b8jpnLyqfGrcV2feUtIZ ------END PRIVATE KEY----- diff --git a/nats/testdata/certs/server.pem b/nats/testdata/certs/server.pem deleted file mode 100644 index 80a9d8fe..00000000 --- a/nats/testdata/certs/server.pem +++ /dev/null @@ -1,99 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 1d:d9:1f:06:dd:fd:90:26:4e:27:ea:2e:01:4b:31:e6:d2:49:31:1f - Signature Algorithm: sha256WithRSAEncryption - Issuer: C=US, ST=California, O=Synadia, OU=nats.io, CN=Certificate Authority 2022-08-27 - Validity - Not Before: Aug 27 20:23:02 2022 GMT - Not After : Aug 24 20:23:02 2032 GMT - Subject: C=US, ST=California, O=Synadia, OU=nats.io, CN=localhost - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:e6:fb:47:65:cd:c9:a2:2d:af:8b:cd:d5:6a:79: - 54:3c:07:5f:eb:5a:71:2b:2b:e5:6f:be:31:fb:16: - 65:68:76:0e:59:e7:e4:57:ca:88:e9:77:d6:41:ad: - 57:7a:42:b2:d2:54:c4:0f:7c:5b:c1:bc:61:97:e3: - 22:3a:3e:1e:4a:5d:47:9f:6b:7d:6f:34:e3:8c:86: - 9d:85:19:29:9a:11:58:44:4c:a1:90:d3:14:61:e1: - 57:da:01:ea:ce:3f:90:ae:9e:5d:13:6d:2c:89:ca: - 39:15:6b:b6:9e:32:d7:2a:4c:48:85:2f:b0:1e:d8: - 4b:62:32:14:eb:32:b6:29:04:34:3c:af:39:b6:8b: - 52:32:4d:bf:43:5f:9b:fb:0d:43:a6:ad:2c:a7:41: - 29:55:c9:70:b3:b5:15:46:34:bf:e4:1e:52:2d:a4: - 49:2e:d5:21:ed:fc:00:f7:a2:0b:bc:12:0a:90:64: - 50:7c:c5:14:70:f5:fb:9b:62:08:78:43:49:31:f3: - 47:b8:93:d4:2d:4c:a9:dc:17:70:76:34:66:ff:65: - c1:39:67:e9:a6:1c:80:6a:f0:9d:b3:28:c8:a3:3a: - b7:5d:de:6e:53:6d:09:b3:0d:b1:13:10:e8:ec:e0: - bd:5e:a1:94:4b:70:bf:dc:bd:8b:b9:82:65:dd:af: - 81:7b - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: - CA:FALSE - Netscape Comment: - nats.io nats-server test-suite certificate - X509v3 Subject Key Identifier: - 2B:8C:A3:8B:DB:DB:5C:CE:18:DB:F6:A8:31:4E:C2:3E:EE:D3:40:7E - X509v3 Authority Key Identifier: - keyid:FE:CF:7F:3E:2E:EE:C4:AD:EA:5B:6F:88:45:6C:C7:88:48:14:8B:3A - DirName:/C=US/ST=California/O=Synadia/OU=nats.io/CN=Certificate Authority 2022-08-27 - serial:49:9C:16:ED:BB:5C:F4:45:1B:AC:C5:AD:8C:7A:5B:33:40:B6:6D:21 - - X509v3 Subject Alternative Name: - DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1 - Netscape Cert Type: - SSL Client, SSL Server - X509v3 Key Usage: - Digital Signature, Key Encipherment - X509v3 Extended Key Usage: - TLS Web Server Authentication, Netscape Server Gated Crypto, Microsoft Server Gated Crypto, TLS Web Client Authentication - Signature Algorithm: sha256WithRSAEncryption - 54:49:34:2b:38:d1:aa:3b:43:60:4c:3f:6a:f8:74:ca:49:53: - a1:af:12:d3:a8:17:90:7b:9d:a3:69:13:6e:da:2c:b7:61:31: - ac:eb:00:93:92:fc:0c:10:d4:18:a0:16:61:94:4b:42:cb:eb: - 7a:f6:80:c6:45:c0:9c:09:aa:a9:48:e8:36:e3:c5:be:36:e0: - e9:78:2a:bb:ab:64:9b:20:eb:e6:0f:63:2b:59:c3:58:0b:3a: - 84:15:04:c1:7e:12:03:1b:09:25:8d:4c:03:e8:18:26:c0:6c: - b7:90:b1:fd:bc:f1:cf:d0:d5:4a:03:15:71:0c:7d:c1:76:87: - 92:f1:3e:bc:75:51:5a:c4:36:a4:ff:91:98:df:33:5d:a7:38: - de:50:29:fd:0f:c8:55:e6:8f:24:c2:2e:98:ab:d9:5d:65:2f: - 50:cc:25:f6:84:f2:21:2e:5e:76:d0:86:1e:69:8b:cb:8a:3a: - 2d:79:21:5e:e7:f7:2d:06:18:a1:13:cb:01:c3:46:91:2a:de: - b4:82:d7:c3:62:6f:08:a1:d5:90:19:30:9d:64:8e:e4:f8:ba: - 4f:2f:ba:13:b4:a3:9f:d1:d5:77:64:8a:3e:eb:53:c5:47:ac: - ab:3e:0e:7a:9b:a6:f4:48:25:66:eb:c7:4c:f9:50:24:eb:71: - e0:75:ae:e6 ------BEGIN CERTIFICATE----- -MIIE+TCCA+GgAwIBAgIUHdkfBt39kCZOJ+ouAUsx5tJJMR8wDQYJKoZIhvcNAQEL -BQAwcTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNVBAoM -B1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xKTAnBgNVBAMMIENlcnRpZmljYXRl -IEF1dGhvcml0eSAyMDIyLTA4LTI3MB4XDTIyMDgyNzIwMjMwMloXDTMyMDgyNDIw -MjMwMlowWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEDAOBgNV -BAoMB1N5bmFkaWExEDAOBgNVBAsMB25hdHMuaW8xEjAQBgNVBAMMCWxvY2FsaG9z -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOb7R2XNyaItr4vN1Wp5 -VDwHX+tacSsr5W++MfsWZWh2Dlnn5FfKiOl31kGtV3pCstJUxA98W8G8YZfjIjo+ -HkpdR59rfW8044yGnYUZKZoRWERMoZDTFGHhV9oB6s4/kK6eXRNtLInKORVrtp4y -1ypMSIUvsB7YS2IyFOsytikENDyvObaLUjJNv0Nfm/sNQ6atLKdBKVXJcLO1FUY0 -v+QeUi2kSS7VIe38APeiC7wSCpBkUHzFFHD1+5tiCHhDSTHzR7iT1C1MqdwXcHY0 -Zv9lwTln6aYcgGrwnbMoyKM6t13eblNtCbMNsRMQ6OzgvV6hlEtwv9y9i7mCZd2v -gXsCAwEAAaOCAZ4wggGaMAkGA1UdEwQCMAAwOQYJYIZIAYb4QgENBCwWKm5hdHMu -aW8gbmF0cy1zZXJ2ZXIgdGVzdC1zdWl0ZSBjZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU -K4yji9vbXM4Y2/aoMU7CPu7TQH4wga4GA1UdIwSBpjCBo4AU/s9/Pi7uxK3qW2+I -RWzHiEgUizqhdaRzMHExCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh -MRAwDgYDVQQKDAdTeW5hZGlhMRAwDgYDVQQLDAduYXRzLmlvMSkwJwYDVQQDDCBD -ZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAyMi0wOC0yN4IUSZwW7btc9EUbrMWtjHpb -M0C2bSEwLAYDVR0RBCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAA -AAABMBEGCWCGSAGG+EIBAQQEAwIGwDALBgNVHQ8EBAMCBaAwNAYDVR0lBC0wKwYI -KwYBBQUHAwEGCWCGSAGG+EIEAQYKKwYBBAGCNwoDAwYIKwYBBQUHAwIwDQYJKoZI -hvcNAQELBQADggEBAFRJNCs40ao7Q2BMP2r4dMpJU6GvEtOoF5B7naNpE27aLLdh -MazrAJOS/AwQ1BigFmGUS0LL63r2gMZFwJwJqqlI6Dbjxb424Ol4KrurZJsg6+YP -YytZw1gLOoQVBMF+EgMbCSWNTAPoGCbAbLeQsf288c/Q1UoDFXEMfcF2h5LxPrx1 -UVrENqT/kZjfM12nON5QKf0PyFXmjyTCLpir2V1lL1DMJfaE8iEuXnbQhh5pi8uK -Oi15IV7n9y0GGKETywHDRpEq3rSC18Nibwih1ZAZMJ1kjuT4uk8vuhO0o5/R1Xdk -ij7rU8VHrKs+DnqbpvRIJWbrx0z5UCTrceB1ruY= ------END CERTIFICATE----- diff --git a/nats/testdata/nats-tls.conf b/nats/testdata/nats-tls.conf index 118515f3..4f3cf20f 100644 --- a/nats/testdata/nats-tls.conf +++ b/nats/testdata/nats-tls.conf @@ -4,7 +4,7 @@ port: 4443 net: 0.0.0.0 # net interface tls { - cert_file: "./testdata/certs/server.pem" - key_file: "./testdata/certs/key.pem" + cert_file: "/tls/server.crt" + key_file: "/tls/server.key" timeout: 2 } From 8832c250afed7de43a637ee1f925ccfce0cbf04d Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Mon, 5 Feb 2024 15:06:04 +0200 Subject: [PATCH 29/32] Update nats_test.go --- nats/nats_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nats/nats_test.go b/nats/nats_test.go index ea6fe324..686aba69 100644 --- a/nats/nats_test.go +++ b/nats/nats_test.go @@ -14,7 +14,7 @@ var config = Config{ NatsOptions: []nats.Option{ nats.MaxReconnects(2), // Enable TLS by specifying RootCAs - nats.RootCAs("./tls/ca.crt"), + nats.RootCAs("../tls/ca.crt"), }, KeyValueConfig: jetstream.KeyValueConfig{ Bucket: "test", From 2a398b076934ee000cd933a50dba81d93e28e26d Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Mon, 5 Feb 2024 15:08:17 +0200 Subject: [PATCH 30/32] use redis cert --- nats/testdata/nats-tls.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nats/testdata/nats-tls.conf b/nats/testdata/nats-tls.conf index 4f3cf20f..a1a5f04d 100644 --- a/nats/testdata/nats-tls.conf +++ b/nats/testdata/nats-tls.conf @@ -4,7 +4,7 @@ port: 4443 net: 0.0.0.0 # net interface tls { - cert_file: "/tls/server.crt" - key_file: "/tls/server.key" + cert_file: "/tls/redis.crt" + key_file: "/tls/redis.key" timeout: 2 } From 689f428ba009d9c1bc1024b299d93d35e04bc7f8 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Mon, 5 Feb 2024 15:10:37 +0200 Subject: [PATCH 31/32] use localhost instead of ip --- nats/nats_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nats/nats_test.go b/nats/nats_test.go index 686aba69..8473e92c 100644 --- a/nats/nats_test.go +++ b/nats/nats_test.go @@ -10,7 +10,7 @@ import ( ) var config = Config{ - URLs: "nats://127.0.0.1:4443", + URLs: "nats://localhost:4443", NatsOptions: []nats.Option{ nats.MaxReconnects(2), // Enable TLS by specifying RootCAs From 4022a5c88f3d0bb204c6bb3d3a82fdaf45028be1 Mon Sep 17 00:00:00 2001 From: Cristian Chiru Date: Tue, 6 Feb 2024 08:17:39 +0200 Subject: [PATCH 32/32] use client certificate authentication --- nats/nats_test.go | 3 ++- nats/testdata/nats-tls.conf | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/nats/nats_test.go b/nats/nats_test.go index 8473e92c..dbe042d7 100644 --- a/nats/nats_test.go +++ b/nats/nats_test.go @@ -13,7 +13,8 @@ var config = Config{ URLs: "nats://localhost:4443", NatsOptions: []nats.Option{ nats.MaxReconnects(2), - // Enable TLS by specifying RootCAs + // Enable TLS with client certificate authentication + nats.ClientCert("../tls/client.crt", "../tls/client.key"), nats.RootCAs("../tls/ca.crt"), }, KeyValueConfig: jetstream.KeyValueConfig{ diff --git a/nats/testdata/nats-tls.conf b/nats/testdata/nats-tls.conf index a1a5f04d..657c1c16 100644 --- a/nats/testdata/nats-tls.conf +++ b/nats/testdata/nats-tls.conf @@ -6,5 +6,6 @@ net: 0.0.0.0 # net interface tls { cert_file: "/tls/redis.crt" key_file: "/tls/redis.key" + ca_file: "/tls/ca.crt" timeout: 2 }