From 5477193ebe3cc79616f04341c0087b45074bd76b Mon Sep 17 00:00:00 2001 From: werbenhu Date: Tue, 26 Mar 2024 20:25:54 +0800 Subject: [PATCH 1/5] Implementing Pebble as a persistence database hook. --- examples/persistence/pebble/main.go | 62 ++ go.mod | 31 +- go.sum | 494 ++++++++++++++- hooks/storage/pebble/pebble.go | 523 ++++++++++++++++ hooks/storage/pebble/pebble_test.go | 903 ++++++++++++++++++++++++++++ hooks/storage/storage.go | 6 + 6 files changed, 2001 insertions(+), 18 deletions(-) create mode 100644 examples/persistence/pebble/main.go create mode 100644 hooks/storage/pebble/pebble.go create mode 100644 hooks/storage/pebble/pebble_test.go diff --git a/examples/persistence/pebble/main.go b/examples/persistence/pebble/main.go new file mode 100644 index 00000000..06268f66 --- /dev/null +++ b/examples/persistence/pebble/main.go @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2022 mochi-mqtt, mochi-co, werbenhu +// SPDX-FileContributor: werbenhu + +package main + +import ( + "log" + "os" + "os/signal" + "syscall" + + mqtt "github.com/mochi-mqtt/server/v2" + "github.com/mochi-mqtt/server/v2/hooks/auth" + "github.com/mochi-mqtt/server/v2/hooks/storage/pebble" + "github.com/mochi-mqtt/server/v2/listeners" +) + +func main() { + pebblePath := ".pebble" + defer os.RemoveAll(pebblePath) // remove the example pebble files at the end + + sigs := make(chan os.Signal, 1) + done := make(chan bool, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-sigs + done <- true + }() + + server := mqtt.New(nil) + _ = server.AddHook(new(auth.AllowHook), nil) + + err := server.AddHook(new(pebble.Hook), &pebble.Options{ + Path: pebblePath, + Mode: pebble.NoSync, + }) + if err != nil { + log.Fatal(err) + } + + tcp := listeners.NewTCP(listeners.Config{ + ID: "t1", + Address: ":1883", + }) + err = server.AddListener(tcp) + if err != nil { + log.Fatal(err) + } + + go func() { + err := server.Serve() + if err != nil { + log.Fatal(err) + } + }() + + <-done + server.Log.Warn("caught signal, stopping...") + _ = server.Close() + server.Log.Info("main.go finished") +} diff --git a/go.mod b/go.mod index 62402a96..4dc706d4 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,13 @@ require ( github.com/alicebob/miniredis/v2 v2.23.0 github.com/asdine/storm v2.1.2+incompatible github.com/asdine/storm/v3 v3.2.1 + github.com/cockroachdb/pebble v1.1.0 + github.com/dgraph-io/badger v1.6.0 github.com/go-redis/redis/v8 v8.11.5 github.com/gorilla/websocket v1.5.0 github.com/jinzhu/copier v0.3.5 github.com/rs/xid v1.4.0 - github.com/stretchr/testify v1.7.1 + github.com/stretchr/testify v1.8.1 github.com/timshannon/badgerhold v1.0.0 go.etcd.io/bbolt v1.3.5 gopkg.in/yaml.v3 v3.0.1 @@ -18,20 +20,37 @@ require ( require ( github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect + github.com/DataDog/zstd v1.4.5 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cockroachdb/errors v1.11.1 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dgraph-io/badger v1.6.0 // indirect github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dustin/go-humanize v1.0.0 // indirect - github.com/golang/protobuf v1.5.0 // indirect - github.com/golang/snappy v0.0.3 // indirect - github.com/google/go-cmp v0.5.8 // indirect + github.com/getsentry/sentry-go v0.18.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/klauspost/compress v1.15.15 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.12.0 // indirect + github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/go.sum b/go.sum index c90351b9..db860e6d 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,51 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM= github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis/v2 v2.23.0 h1:+lwAJYjvvdIVg6doFHuotFjueJ/7KY10xo/vm3X3Scw= @@ -15,15 +55,37 @@ github.com/asdine/storm v2.1.2+incompatible h1:dczuIkyqwY2LrtXPz8ixMrU/OFgZp71kb github.com/asdine/storm v2.1.2+incompatible/go.mod h1:RarYDc9hq1UPLImuiXK3BIWPJLdIygvV3PsInK0FbVQ= github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac= github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8= +github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4= +github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -35,35 +97,134 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= +github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= @@ -71,14 +232,47 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042 github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg= +github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y= +github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= @@ -86,54 +280,330 @@ github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb6 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/timshannon/badgerhold v1.0.0 h1:LtqnDRVP7294FWRiZCIfQa6Tt0bGmlzbO8c364QC2Y8= github.com/timshannon/badgerhold v1.0.0/go.mod h1:Vv2Jj0PAfzqViEpGvJzLP8PY07x1iXLgKRuLY7bqPOE= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/hooks/storage/pebble/pebble.go b/hooks/storage/pebble/pebble.go new file mode 100644 index 00000000..7719e87e --- /dev/null +++ b/hooks/storage/pebble/pebble.go @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2022 mochi-mqtt, mochi-co +// SPDX-FileContributor: mochi-co, gsagula + +package pebble + +import ( + "bytes" + "errors" + "fmt" + "strings" + + pebbledb "github.com/cockroachdb/pebble" + mqtt "github.com/mochi-mqtt/server/v2" + "github.com/mochi-mqtt/server/v2/hooks/storage" + "github.com/mochi-mqtt/server/v2/packets" + "github.com/mochi-mqtt/server/v2/system" +) + +const ( + // defaultDbFile is the default file path for the pebble db file. + defaultDbFile = ".pebble" +) + +// clientKey returns a primary key for a client. +func clientKey(cl *mqtt.Client) string { + return storage.ClientKey + "_" + cl.ID +} + +// subscriptionKey returns a primary key for a subscription. +func subscriptionKey(cl *mqtt.Client, filter string) string { + return storage.SubscriptionKey + "_" + cl.ID + ":" + filter +} + +// retainedKey returns a primary key for a retained message. +func retainedKey(topic string) string { + return storage.RetainedKey + "_" + topic +} + +// inflightKey returns a primary key for an inflight message. +func inflightKey(cl *mqtt.Client, pk packets.Packet) string { + return storage.InflightKey + "_" + cl.ID + ":" + pk.FormatID() +} + +// sysInfoKey returns a primary key for system info. +func sysInfoKey() string { + return storage.SysInfoKey +} + +// keyUpperBound returns the upper bound for a given byte slice by incrementing the last byte. +// It returns nil if all bytes are incremented and equal to 0. +func keyUpperBound(b []byte) []byte { + end := make([]byte, len(b)) + copy(end, b) + for i := len(end) - 1; i >= 0; i-- { + end[i] = end[i] + 1 + if end[i] != 0 { + return end[:i+1] + } + } + return nil +} + +const ( + NoSync = "NoSync" // NoSync specifies the default write options for writes which do not synchronize to disk. + Sync = "Sync" // Sync specifies the default write options for writes which synchronize to disk. +) + +// Options contains configuration settings for the pebble DB instance. +type Options struct { + Options *pebbledb.Options + Mode string `yaml:"mode" json:"mode"` + Path string `yaml:"path" json:"path"` +} + +// Hook is a persistent storage hook based using pebble DB file store as a backend. +type Hook struct { + mqtt.HookBase + config *Options // options for configuring the pebble DB instance. + db *pebbledb.DB // the pebble DB instance + mode *pebbledb.WriteOptions // mode holds the optional per-query parameters for Set and Delete operations +} + +// ID returns the id of the hook. +func (h *Hook) ID() string { + return "pebble-db" +} + +// Provides indicates which hook methods this hook provides. +func (h *Hook) Provides(b byte) bool { + return bytes.Contains([]byte{ + mqtt.OnSessionEstablished, + mqtt.OnDisconnect, + mqtt.OnSubscribed, + mqtt.OnUnsubscribed, + mqtt.OnRetainMessage, + mqtt.OnWillSent, + mqtt.OnQosPublish, + mqtt.OnQosComplete, + mqtt.OnQosDropped, + mqtt.OnSysInfoTick, + mqtt.OnClientExpired, + mqtt.OnRetainedExpired, + mqtt.StoredClients, + mqtt.StoredInflightMessages, + mqtt.StoredRetainedMessages, + mqtt.StoredSubscriptions, + mqtt.StoredSysInfo, + }, []byte{b}) +} + +// Init initializes and connects to the pebble instance. +func (h *Hook) Init(config any) error { + if _, ok := config.(*Options); !ok && config != nil { + return mqtt.ErrInvalidConfigType + } + + if config == nil { + h.config = new(Options) + } else { + h.config = config.(*Options) + } + + if h.config.Path == "" { + h.config.Path = defaultDbFile + } + + if h.config.Options == nil { + h.config.Options = &pebbledb.Options{} + } + + h.mode = pebbledb.NoSync + if strings.EqualFold(h.config.Mode, "Sync") { + h.mode = pebbledb.Sync + } + + var err error + h.db, err = pebbledb.Open(h.config.Path, h.config.Options) + if err != nil { + return err + } + + return nil +} + +// Stop closes the pebble instance. +func (h *Hook) Stop() error { + err := h.db.Close() + h.db = nil + return err +} + +// OnSessionEstablished adds a client to the store when their session is established. +func (h *Hook) OnSessionEstablished(cl *mqtt.Client, pk packets.Packet) { + h.updateClient(cl) +} + +// OnWillSent is called when a client sends a Will Message and the Will Message is removed from the client record. +func (h *Hook) OnWillSent(cl *mqtt.Client, pk packets.Packet) { + h.updateClient(cl) +} + +// updateClient writes the client data to the store. +func (h *Hook) updateClient(cl *mqtt.Client) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + + props := cl.Properties.Props.Copy(false) + in := &storage.Client{ + ID: cl.ID, + T: storage.ClientKey, + Remote: cl.Net.Remote, + Listener: cl.Net.Listener, + Username: cl.Properties.Username, + Clean: cl.Properties.Clean, + ProtocolVersion: cl.Properties.ProtocolVersion, + Properties: storage.ClientProperties{ + SessionExpiryInterval: props.SessionExpiryInterval, + AuthenticationMethod: props.AuthenticationMethod, + AuthenticationData: props.AuthenticationData, + RequestProblemInfo: props.RequestProblemInfo, + RequestResponseInfo: props.RequestResponseInfo, + ReceiveMaximum: props.ReceiveMaximum, + TopicAliasMaximum: props.TopicAliasMaximum, + User: props.User, + MaximumPacketSize: props.MaximumPacketSize, + }, + Will: storage.ClientWill(cl.Properties.Will), + } + h.setKv(clientKey(cl), in) +} + +// OnDisconnect removes a client from the store if their session has expired. +func (h *Hook) OnDisconnect(cl *mqtt.Client, _ error, expire bool) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + + h.updateClient(cl) + + if !expire { + return + } + + if errors.Is(cl.StopCause(), packets.ErrSessionTakenOver) { + return + } + + h.delKv(clientKey(cl)) +} + +// OnSubscribed adds one or more client subscriptions to the store. +func (h *Hook) OnSubscribed(cl *mqtt.Client, pk packets.Packet, reasonCodes []byte) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + + var in *storage.Subscription + for i := 0; i < len(pk.Filters); i++ { + in = &storage.Subscription{ + ID: subscriptionKey(cl, pk.Filters[i].Filter), + T: storage.SubscriptionKey, + Client: cl.ID, + Qos: reasonCodes[i], + Filter: pk.Filters[i].Filter, + Identifier: pk.Filters[i].Identifier, + NoLocal: pk.Filters[i].NoLocal, + RetainHandling: pk.Filters[i].RetainHandling, + RetainAsPublished: pk.Filters[i].RetainAsPublished, + } + h.setKv(in.ID, in) + } +} + +// OnUnsubscribed removes one or more client subscriptions from the store. +func (h *Hook) OnUnsubscribed(cl *mqtt.Client, pk packets.Packet) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + + for i := 0; i < len(pk.Filters); i++ { + h.delKv(subscriptionKey(cl, pk.Filters[i].Filter)) + } +} + +// OnRetainMessage adds a retained message for a topic to the store. +func (h *Hook) OnRetainMessage(cl *mqtt.Client, pk packets.Packet, r int64) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + + if r == -1 { + h.delKv(retainedKey(pk.TopicName)) + return + } + + props := pk.Properties.Copy(false) + in := &storage.Message{ + ID: retainedKey(pk.TopicName), + T: storage.RetainedKey, + FixedHeader: pk.FixedHeader, + TopicName: pk.TopicName, + Payload: pk.Payload, + Created: pk.Created, + Origin: pk.Origin, + Properties: storage.MessageProperties{ + PayloadFormat: props.PayloadFormat, + MessageExpiryInterval: props.MessageExpiryInterval, + ContentType: props.ContentType, + ResponseTopic: props.ResponseTopic, + CorrelationData: props.CorrelationData, + SubscriptionIdentifier: props.SubscriptionIdentifier, + TopicAlias: props.TopicAlias, + User: props.User, + }, + } + + h.setKv(in.ID, in) +} + +// OnQosPublish adds or updates an inflight message in the store. +func (h *Hook) OnQosPublish(cl *mqtt.Client, pk packets.Packet, sent int64, resends int) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + + props := pk.Properties.Copy(false) + in := &storage.Message{ + ID: inflightKey(cl, pk), + T: storage.InflightKey, + Origin: pk.Origin, + PacketID: pk.PacketID, + FixedHeader: pk.FixedHeader, + TopicName: pk.TopicName, + Payload: pk.Payload, + Sent: sent, + Created: pk.Created, + Properties: storage.MessageProperties{ + PayloadFormat: props.PayloadFormat, + MessageExpiryInterval: props.MessageExpiryInterval, + ContentType: props.ContentType, + ResponseTopic: props.ResponseTopic, + CorrelationData: props.CorrelationData, + SubscriptionIdentifier: props.SubscriptionIdentifier, + TopicAlias: props.TopicAlias, + User: props.User, + }, + } + h.setKv(in.ID, in) +} + +// OnQosComplete removes a resolved inflight message from the store. +func (h *Hook) OnQosComplete(cl *mqtt.Client, pk packets.Packet) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + h.delKv(inflightKey(cl, pk)) +} + +// OnQosDropped removes a dropped inflight message from the store. +func (h *Hook) OnQosDropped(cl *mqtt.Client, pk packets.Packet) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + } + + h.OnQosComplete(cl, pk) +} + +// OnSysInfoTick stores the latest system info in the store. +func (h *Hook) OnSysInfoTick(sys *system.Info) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + + in := &storage.SystemInfo{ + ID: sysInfoKey(), + T: storage.SysInfoKey, + Info: *sys.Clone(), + } + h.setKv(in.ID, in) +} + +// OnRetainedExpired deletes expired retained messages from the store. +func (h *Hook) OnRetainedExpired(filter string) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + h.delKv(retainedKey(filter)) +} + +// OnClientExpired deleted expired clients from the store. +func (h *Hook) OnClientExpired(cl *mqtt.Client) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + h.delKv(clientKey(cl)) +} + +// StoredClients returns all stored clients from the store. +func (h *Hook) StoredClients() (v []storage.Client, err error) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + + iter, _ := h.db.NewIter(&pebbledb.IterOptions{ + LowerBound: []byte(storage.ClientKey), + UpperBound: keyUpperBound([]byte(storage.ClientKey)), + }) + + for iter.First(); iter.Valid(); iter.Next() { + item := storage.Client{} + if err := item.UnmarshalBinary(iter.Value()); err == nil { + v = append(v, item) + } + } + return v, nil +} + +// StoredSubscriptions returns all stored subscriptions from the store. +func (h *Hook) StoredSubscriptions() (v []storage.Subscription, err error) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + + iter, _ := h.db.NewIter(&pebbledb.IterOptions{ + LowerBound: []byte(storage.SubscriptionKey), + UpperBound: keyUpperBound([]byte(storage.SubscriptionKey)), + }) + + for iter.First(); iter.Valid(); iter.Next() { + item := storage.Subscription{} + if err := item.UnmarshalBinary(iter.Value()); err == nil { + v = append(v, item) + } + } + return v, nil +} + +// StoredRetainedMessages returns all stored retained messages from the store. +func (h *Hook) StoredRetainedMessages() (v []storage.Message, err error) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + + iter, _ := h.db.NewIter(&pebbledb.IterOptions{ + LowerBound: []byte(storage.RetainedKey), + UpperBound: keyUpperBound([]byte(storage.RetainedKey)), + }) + + for iter.First(); iter.Valid(); iter.Next() { + item := storage.Message{} + if err := item.UnmarshalBinary(iter.Value()); err == nil { + v = append(v, item) + } + } + return v, nil +} + +// StoredInflightMessages returns all stored inflight messages from the store. +func (h *Hook) StoredInflightMessages() (v []storage.Message, err error) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + + iter, _ := h.db.NewIter(&pebbledb.IterOptions{ + LowerBound: []byte(storage.InflightKey), + UpperBound: keyUpperBound([]byte(storage.InflightKey)), + }) + + for iter.First(); iter.Valid(); iter.Next() { + item := storage.Message{} + if err := item.UnmarshalBinary(iter.Value()); err == nil { + v = append(v, item) + } + } + return v, nil +} + +// StoredSysInfo returns the system info from the store. +func (h *Hook) StoredSysInfo() (v storage.SystemInfo, err error) { + if h.db == nil { + h.Log.Error("", "error", storage.ErrDBFileNotOpen) + return + } + + err = h.getKv(sysInfoKey(), &v) + if err != nil && !errors.Is(err, pebbledb.ErrNotFound) { + return + } + return v, nil +} + +// Errorf satisfies the pebble interface for an error logger. +func (h *Hook) Errorf(m string, v ...interface{}) { + h.Log.Error(fmt.Sprintf(strings.ToLower(strings.Trim(m, "\n")), v...), "v", v) + +} + +// Warningf satisfies the pebble interface for a warning logger. +func (h *Hook) Warningf(m string, v ...interface{}) { + h.Log.Warn(fmt.Sprintf(strings.ToLower(strings.Trim(m, "\n")), v...), "v", v) +} + +// Infof satisfies the pebble interface for an info logger. +func (h *Hook) Infof(m string, v ...interface{}) { + h.Log.Info(fmt.Sprintf(strings.ToLower(strings.Trim(m, "\n")), v...), "v", v) +} + +// Debugf satisfies the pebble interface for a debug logger. +func (h *Hook) Debugf(m string, v ...interface{}) { + h.Log.Debug(fmt.Sprintf(strings.ToLower(strings.Trim(m, "\n")), v...), "v", v) +} + +// delKv deletes a key-value pair from the database. +func (h *Hook) delKv(key string) error { + err := h.db.Delete([]byte(key), h.mode) + if err != nil { + h.Log.Error("failed to delete data", "error", err, "key", key) + return err + } + return nil +} + +// setKv stores a key-value pair in the database. +func (h *Hook) setKv(key string, obj storage.Serializable) error { + bs, _ := obj.MarshalBinary() + err := h.db.Set([]byte(key), bs, h.mode) + if err != nil { + h.Log.Error("failed to update data", "error", err, "key", key) + return err + } + return nil +} + +// getKv retrieves the value associated with a key from the database. +func (h *Hook) getKv(key string, obj storage.Serializable) error { + value, closer, err := h.db.Get([]byte(key)) + if err != nil { + return err + } + + defer func() { + if closer != nil { + closer.Close() + } + }() + return obj.UnmarshalBinary(value) +} diff --git a/hooks/storage/pebble/pebble_test.go b/hooks/storage/pebble/pebble_test.go new file mode 100644 index 00000000..1a25e324 --- /dev/null +++ b/hooks/storage/pebble/pebble_test.go @@ -0,0 +1,903 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2022 mochi-mqtt, mochi-co +// SPDX-FileContributor: mochi-co + +package pebble + +import ( + "log/slog" + "os" + "strings" + "testing" + "time" + + pebbledb "github.com/cockroachdb/pebble" + mqtt "github.com/mochi-mqtt/server/v2" + "github.com/mochi-mqtt/server/v2/hooks/storage" + "github.com/mochi-mqtt/server/v2/packets" + "github.com/mochi-mqtt/server/v2/system" + "github.com/stretchr/testify/require" +) + +var ( + logger = slog.New(slog.NewTextHandler(os.Stdout, nil)) + + client = &mqtt.Client{ + ID: "test", + Net: mqtt.ClientConnection{ + Remote: "test.addr", + Listener: "listener", + }, + Properties: mqtt.ClientProperties{ + Username: []byte("username"), + Clean: false, + }, + } + + pkf = packets.Packet{Filters: packets.Subscriptions{{Filter: "a/b/c"}}} + + opts = []struct { + name string + opt *Options + }{ + { + name: "NoSync", + opt: &Options{ + Mode: NoSync, + }, + }, + { + name: "Sync", + opt: &Options{ + Mode: Sync, + }, + }, + } +) + +func teardown(t *testing.T, path string, h *Hook) { + _ = h.Stop() + err := os.RemoveAll("./" + strings.Replace(path, "..", "", -1)) + require.NoError(t, err) +} + +func TestKeyUpperBound(t *testing.T) { + // Test case 1: Non-nil case + input1 := []byte{97, 98, 99} // "abc" + require.NotNil(t, keyUpperBound(input1)) + + // Test case 2: All bytes are 255 + input2 := []byte{255, 255, 255} + require.Nil(t, keyUpperBound(input2)) + + // Test case 3: Empty slice + input3 := []byte{} + require.Nil(t, keyUpperBound(input3)) + + // Test case 4: Nil case + input4 := []byte{255, 255, 255} + require.Nil(t, keyUpperBound(input4)) +} + +func TestClientKey(t *testing.T) { + k := clientKey(&mqtt.Client{ID: "cl1"}) + require.Equal(t, storage.ClientKey+"_cl1", k) +} + +func TestSubscriptionKey(t *testing.T) { + k := subscriptionKey(&mqtt.Client{ID: "cl1"}, "a/b/c") + require.Equal(t, storage.SubscriptionKey+"_cl1:a/b/c", k) +} + +func TestRetainedKey(t *testing.T) { + k := retainedKey("a/b/c") + require.Equal(t, storage.RetainedKey+"_a/b/c", k) +} + +func TestInflightKey(t *testing.T) { + k := inflightKey(&mqtt.Client{ID: "cl1"}, packets.Packet{PacketID: 1}) + require.Equal(t, storage.InflightKey+"_cl1:1", k) +} + +func TestSysInfoKey(t *testing.T) { + require.Equal(t, storage.SysInfoKey, sysInfoKey()) +} + +func TestID(t *testing.T) { + h := new(Hook) + require.Equal(t, "pebble-db", h.ID()) +} + +func TestProvides(t *testing.T) { + h := new(Hook) + require.True(t, h.Provides(mqtt.OnSessionEstablished)) + require.True(t, h.Provides(mqtt.OnDisconnect)) + require.True(t, h.Provides(mqtt.OnSubscribed)) + require.True(t, h.Provides(mqtt.OnUnsubscribed)) + require.True(t, h.Provides(mqtt.OnRetainMessage)) + require.True(t, h.Provides(mqtt.OnQosPublish)) + require.True(t, h.Provides(mqtt.OnQosComplete)) + require.True(t, h.Provides(mqtt.OnQosDropped)) + require.True(t, h.Provides(mqtt.OnSysInfoTick)) + require.True(t, h.Provides(mqtt.StoredClients)) + require.True(t, h.Provides(mqtt.StoredInflightMessages)) + require.True(t, h.Provides(mqtt.StoredRetainedMessages)) + require.True(t, h.Provides(mqtt.StoredSubscriptions)) + require.True(t, h.Provides(mqtt.StoredSysInfo)) + require.False(t, h.Provides(mqtt.OnACLCheck)) + require.False(t, h.Provides(mqtt.OnConnectAuthenticate)) +} + +func TestInitBadConfig(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + + err := h.Init(map[string]any{}) + require.Error(t, err) +} + +func TestInitBadPath(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + + err := h.Init(&Options{ + Path: "\n\r", + }) + require.Error(t, err) +} + +func TestInitUseDefaults(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + require.Equal(t, defaultDbFile, h.config.Path) +} + +func TestOnSessionEstablishedThenOnDisconnect(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + h.OnSessionEstablished(client, packets.Packet{}) + + r := new(storage.Client) + err = h.getKv(clientKey(client), r) + require.NoError(t, err) + require.Equal(t, client.ID, r.ID) + require.Equal(t, client.Properties.Username, r.Username) + require.Equal(t, client.Properties.Clean, r.Clean) + require.Equal(t, client.Net.Remote, r.Remote) + require.Equal(t, client.Net.Listener, r.Listener) + require.NotSame(t, client, r) + + h.OnDisconnect(client, nil, false) + r2 := new(storage.Client) + err = h.getKv(clientKey(client), r2) + require.NoError(t, err) + require.Equal(t, client.ID, r.ID) + + h.OnDisconnect(client, nil, true) + r3 := new(storage.Client) + err = h.getKv(clientKey(client), r3) + require.Error(t, err) + require.ErrorIs(t, pebbledb.ErrNotFound, err) + require.Empty(t, r3.ID) + }) + } +} + +func TestOnClientExpired(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + cl := &mqtt.Client{ID: "cl1"} + clientKey := clientKey(cl) + + err = h.setKv(clientKey, &storage.Client{ID: cl.ID}) + require.NoError(t, err) + + r := new(storage.Client) + err = h.getKv(clientKey, r) + require.NoError(t, err) + require.Equal(t, cl.ID, r.ID) + + h.OnClientExpired(cl) + err = h.getKv(clientKey, r) + require.Error(t, err) + require.ErrorIs(t, err, pebbledb.ErrNotFound) + }) + } +} + +func TestOnClientExpiredNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + h.OnClientExpired(client) +} + +func TestOnClientExpiredClosedDB(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnClientExpired(client) + }) + } +} + +func TestOnSessionEstablishedNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + h.OnSessionEstablished(client, packets.Packet{}) +} + +func TestOnSessionEstablishedClosedDB(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnSessionEstablished(client, packets.Packet{}) + }) + } +} + +func TestOnWillSent(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + c1 := client + c1.Properties.Will.Flag = 1 + h.OnWillSent(c1, packets.Packet{}) + + r := new(storage.Client) + err = h.getKv(clientKey(client), r) + require.NoError(t, err) + + require.Equal(t, uint32(1), r.Will.Flag) + require.NotSame(t, client, r) + }) + } +} + +func TestOnDisconnectNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + h.OnDisconnect(client, nil, false) +} + +func TestOnDisconnectClosedDB(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnDisconnect(client, nil, false) + }) + } +} + +func TestOnDisconnectSessionTakenOver(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + + testClient := &mqtt.Client{ + ID: "test", + Net: mqtt.ClientConnection{ + Remote: "test.addr", + Listener: "listener", + }, + Properties: mqtt.ClientProperties{ + Username: []byte("username"), + Clean: false, + }, + } + + testClient.Stop(packets.ErrSessionTakenOver) + h.OnDisconnect(testClient, nil, true) + teardown(t, h.config.Path, h) + }) + } +} + +func TestOnSubscribedThenOnUnsubscribed(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + h.OnSubscribed(client, pkf, []byte{0}) + r := new(storage.Subscription) + + err = h.getKv(subscriptionKey(client, pkf.Filters[0].Filter), r) + require.NoError(t, err) + require.Equal(t, client.ID, r.Client) + require.Equal(t, pkf.Filters[0].Filter, r.Filter) + require.Equal(t, byte(0), r.Qos) + + h.OnUnsubscribed(client, pkf) + err = h.getKv(subscriptionKey(client, pkf.Filters[0].Filter), r) + require.Error(t, err) + require.Equal(t, pebbledb.ErrNotFound, err) + }) + } +} + +func TestOnSubscribedNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + h.OnSubscribed(client, pkf, []byte{0}) +} + +func TestOnSubscribedClosedDB(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnSubscribed(client, pkf, []byte{0}) + }) + } +} + +func TestOnUnsubscribedNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + h.OnUnsubscribed(client, pkf) +} + +func TestOnUnsubscribedClosedDB(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnUnsubscribed(client, pkf) + }) + } +} + +func TestOnRetainMessageThenUnset(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + pk := packets.Packet{ + FixedHeader: packets.FixedHeader{ + Retain: true, + }, + Payload: []byte("hello"), + TopicName: "a/b/c", + } + + h.OnRetainMessage(client, pk, 1) + + r := new(storage.Message) + err = h.getKv(retainedKey(pk.TopicName), r) + require.NoError(t, err) + require.Equal(t, pk.TopicName, r.TopicName) + require.Equal(t, pk.Payload, r.Payload) + + h.OnRetainMessage(client, pk, -1) + err = h.getKv(retainedKey(pk.TopicName), r) + require.Error(t, err) + require.ErrorIs(t, err, pebbledb.ErrNotFound) + + // coverage: delete deleted + h.OnRetainMessage(client, pk, -1) + err = h.getKv(retainedKey(pk.TopicName), r) + require.Error(t, err) + require.ErrorIs(t, err, pebbledb.ErrNotFound) + }) + } +} + +func TestOnRetainedExpired(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + m := &storage.Message{ + ID: retainedKey("a/b/c"), + T: storage.RetainedKey, + TopicName: "a/b/c", + } + + err = h.setKv(m.ID, m) + require.NoError(t, err) + + r := new(storage.Message) + err = h.getKv(m.ID, r) + require.NoError(t, err) + require.Equal(t, m.TopicName, r.TopicName) + + h.OnRetainedExpired(m.TopicName) + err = h.getKv(m.ID, r) + require.Error(t, err) + require.ErrorIs(t, err, pebbledb.ErrNotFound) + }) + } +} + +func TestOnRetainExpiredNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + h.OnRetainedExpired("a/b/c") +} + +func TestOnRetainExpiredClosedDB(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnRetainedExpired("a/b/c") + }) + } +} + +func TestOnRetainMessageNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + h.OnRetainMessage(client, packets.Packet{}, 0) +} + +func TestOnRetainMessageClosedDB(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnRetainMessage(client, packets.Packet{}, 0) + }) + } +} + +func TestOnQosPublishThenQOSComplete(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + pk := packets.Packet{ + FixedHeader: packets.FixedHeader{ + Retain: true, + Qos: 2, + }, + Payload: []byte("hello"), + TopicName: "a/b/c", + } + + h.OnQosPublish(client, pk, time.Now().Unix(), 0) + + r := new(storage.Message) + err = h.getKv(inflightKey(client, pk), r) + require.NoError(t, err) + require.Equal(t, pk.TopicName, r.TopicName) + require.Equal(t, pk.Payload, r.Payload) + + // ensure dates are properly saved + require.True(t, r.Sent > 0) + require.True(t, time.Now().Unix()-1 < r.Sent) + + // OnQosDropped is a passthrough to OnQosComplete here + h.OnQosDropped(client, pk) + err = h.getKv(inflightKey(client, pk), r) + require.Error(t, err) + require.ErrorIs(t, err, pebbledb.ErrNotFound) + }) + } +} + +func TestOnQosPublishNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + h.OnQosPublish(client, packets.Packet{}, time.Now().Unix(), 0) +} + +func TestOnQosPublishClosedDB(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnQosPublish(client, packets.Packet{}, time.Now().Unix(), 0) + }) + } +} + +func TestOnQosCompleteNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + h.OnQosComplete(client, packets.Packet{}) +} + +func TestOnQosCompleteClosedDB(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnQosComplete(client, packets.Packet{}) + }) + } +} + +func TestOnQosDroppedNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + h.OnQosDropped(client, packets.Packet{}) +} + +func TestOnSysInfoTick(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + info := &system.Info{ + Version: "2.0.0", + BytesReceived: 100, + } + + h.OnSysInfoTick(info) + + r := new(storage.SystemInfo) + err = h.getKv(storage.SysInfoKey, r) + require.NoError(t, err) + require.Equal(t, info.Version, r.Version) + require.Equal(t, info.BytesReceived, r.BytesReceived) + require.NotSame(t, info, r) + }) + } +} + +func TestOnSysInfoTickNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + h.OnSysInfoTick(new(system.Info)) +} + +func TestOnSysInfoTickClosedDB(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnSysInfoTick(new(system.Info)) + }) + } +} + +func TestStoredClients(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + // populate with clients + err = h.setKv(storage.ClientKey+"_cl1", &storage.Client{ID: "cl1"}) + require.NoError(t, err) + + err = h.setKv(storage.ClientKey+"_cl2", &storage.Client{ID: "cl2"}) + require.NoError(t, err) + + err = h.setKv(storage.ClientKey+"_cl3", &storage.Client{ID: "cl3"}) + require.NoError(t, err) + + r, err := h.StoredClients() + require.NoError(t, err) + require.Len(t, r, 3) + require.Equal(t, "cl1", r[0].ID) + require.Equal(t, "cl2", r[1].ID) + require.Equal(t, "cl3", r[2].ID) + }) + } +} + +func TestStoredClientsNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + v, err := h.StoredClients() + require.Empty(t, v) + require.NoError(t, err) +} + +func TestStoredSubscriptions(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + // populate with subscriptions + err = h.setKv(storage.SubscriptionKey+"_sub1", &storage.Subscription{ID: "sub1"}) + require.NoError(t, err) + + err = h.setKv(storage.SubscriptionKey+"_sub2", &storage.Subscription{ID: "sub2"}) + require.NoError(t, err) + + err = h.setKv(storage.SubscriptionKey+"_sub3", &storage.Subscription{ID: "sub3"}) + require.NoError(t, err) + + r, err := h.StoredSubscriptions() + require.NoError(t, err) + require.Len(t, r, 3) + require.Equal(t, "sub1", r[0].ID) + require.Equal(t, "sub2", r[1].ID) + require.Equal(t, "sub3", r[2].ID) + }) + } +} + +func TestStoredSubscriptionsNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + v, err := h.StoredSubscriptions() + require.Empty(t, v) + require.NoError(t, err) +} + +func TestStoredRetainedMessages(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + // populate with messages + err = h.setKv(storage.RetainedKey+"_m1", &storage.Message{ID: "m1"}) + require.NoError(t, err) + + err = h.setKv(storage.RetainedKey+"_m2", &storage.Message{ID: "m2"}) + require.NoError(t, err) + + err = h.setKv(storage.RetainedKey+"_m3", &storage.Message{ID: "m3"}) + require.NoError(t, err) + + err = h.setKv(storage.InflightKey+"_i3", &storage.Message{ID: "i3"}) + require.NoError(t, err) + + r, err := h.StoredRetainedMessages() + require.NoError(t, err) + require.Len(t, r, 3) + require.Equal(t, "m1", r[0].ID) + require.Equal(t, "m2", r[1].ID) + require.Equal(t, "m3", r[2].ID) + }) + } +} + +func TestStoredRetainedMessagesNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + v, err := h.StoredRetainedMessages() + require.Empty(t, v) + require.NoError(t, err) +} + +func TestStoredInflightMessages(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + // populate with messages + err = h.setKv(storage.InflightKey+"_i1", &storage.Message{ID: "i1"}) + require.NoError(t, err) + + err = h.setKv(storage.InflightKey+"_i2", &storage.Message{ID: "i2"}) + require.NoError(t, err) + + err = h.setKv(storage.InflightKey+"_i3", &storage.Message{ID: "i3"}) + require.NoError(t, err) + + err = h.setKv(storage.RetainedKey+"_m1", &storage.Message{ID: "m1"}) + require.NoError(t, err) + + r, err := h.StoredInflightMessages() + require.NoError(t, err) + require.Len(t, r, 3) + require.Equal(t, "i1", r[0].ID) + require.Equal(t, "i2", r[1].ID) + require.Equal(t, "i3", r[2].ID) + }) + } +} + +func TestStoredInflightMessagesNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + v, err := h.StoredInflightMessages() + require.Empty(t, v) + require.NoError(t, err) +} + +func TestStoredSysInfo(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + // populate with messages + err = h.setKv(storage.SysInfoKey, &storage.SystemInfo{ + ID: storage.SysInfoKey, + Info: system.Info{ + Version: "2.0.0", + }, + T: storage.SysInfoKey, + }) + require.NoError(t, err) + + r, err := h.StoredSysInfo() + require.NoError(t, err) + require.Equal(t, "2.0.0", r.Info.Version) + }) + } +} + +func TestStoredSysInfoNoDB(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + v, err := h.StoredSysInfo() + require.Empty(t, v) + require.NoError(t, err) +} + +func TestErrorf(t *testing.T) { + // coverage: one day check log hook + h := new(Hook) + h.SetOpts(logger, nil) + h.Errorf("test", 1, 2, 3) +} + +func TestWarningf(t *testing.T) { + // coverage: one day check log hook + h := new(Hook) + h.SetOpts(logger, nil) + h.Warningf("test", 1, 2, 3) +} + +func TestInfof(t *testing.T) { + // coverage: one day check log hook + h := new(Hook) + h.SetOpts(logger, nil) + h.Infof("test", 1, 2, 3) +} + +func TestDebugf(t *testing.T) { + // coverage: one day check log hook + h := new(Hook) + h.SetOpts(logger, nil) + h.Debugf("test", 1, 2, 3) +} + +func TestGetDelKv(t *testing.T) { + for _, tt := range opts { + t.Run(tt.name, func(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(tt.opt) + defer teardown(t, h.config.Path, h) + require.NoError(t, err) + + err = h.setKv("testKey", &storage.Client{ID: "testId"}) + require.NoError(t, err) + + var obj storage.Client + err = h.getKv("testKey", &obj) + require.NoError(t, err) + + err = h.delKv("testKey") + require.NoError(t, err) + + err = h.getKv("testKey", &obj) + require.Error(t, err) + require.ErrorIs(t, pebbledb.ErrNotFound, err) + }) + } +} + +func TestGetDelKvErr(t *testing.T) { + h := new(Hook) + h.SetOpts(logger, nil) + + err := h.Init(&Options{ + Mode: Sync, + }) + require.NoError(t, err) + + err = h.setKv("testKey", &storage.Client{ID: "testId"}) + require.NoError(t, err) + h.Stop() + + h = new(Hook) + h.SetOpts(logger, nil) + + err = h.Init(&Options{ + Mode: Sync, + Options: &pebbledb.Options{ + ReadOnly: true, + }, + }) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) + + err = h.setKv("testKey", &storage.Client{ID: "testId"}) + require.Error(t, err) + + err = h.delKv("testKey") + require.Error(t, err) +} diff --git a/hooks/storage/storage.go b/hooks/storage/storage.go index eb57508f..b83924b3 100644 --- a/hooks/storage/storage.go +++ b/hooks/storage/storage.go @@ -25,6 +25,12 @@ var ( ErrDBFileNotOpen = errors.New("db file not open") ) +// Serializable is an interface for objects that can be serialized and deserialized. +type Serializable interface { + UnmarshalBinary([]byte) error + MarshalBinary() (data []byte, err error) +} + // Client is a storable representation of an MQTT client. type Client struct { Will ClientWill `json:"will"` // will topic and payload data if applicable From fc0a26c0a3031556518ad7dde6888572fa401869 Mon Sep 17 00:00:00 2001 From: werbenhu Date: Tue, 26 Mar 2024 20:38:03 +0800 Subject: [PATCH 2/5] Fixed failing test cases. --- hooks/storage/pebble/pebble_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hooks/storage/pebble/pebble_test.go b/hooks/storage/pebble/pebble_test.go index 1a25e324..22a0bea1 100644 --- a/hooks/storage/pebble/pebble_test.go +++ b/hooks/storage/pebble/pebble_test.go @@ -136,12 +136,14 @@ func TestInitBadConfig(t *testing.T) { require.Error(t, err) } -func TestInitBadPath(t *testing.T) { +func TestInitErr(t *testing.T) { h := new(Hook) h.SetOpts(logger, nil) err := h.Init(&Options{ - Path: "\n\r", + Options: &pebbledb.Options{ + ReadOnly: true, + }, }) require.Error(t, err) } From 60f15c6f3581fed38e0a8168ce907a6614f1e569 Mon Sep 17 00:00:00 2001 From: werbenhu Date: Wed, 27 Mar 2024 10:53:59 +0800 Subject: [PATCH 3/5] Add Pebble DB configuration for file-based configuration, optimize part of the code. --- README-CN.md | 21 +++++++++++++++++++-- README.md | 18 ++++++++++++++++-- config/config.go | 10 ++++++++++ config/config_test.go | 21 +++++++++++++++++++++ examples/config/config.json | 4 ++++ examples/config/config.yaml | 3 +++ hooks/storage/pebble/pebble.go | 22 +++++++++++----------- 7 files changed, 84 insertions(+), 15 deletions(-) diff --git a/README-CN.md b/README-CN.md index 9e1dded7..6bc147dd 100644 --- a/README-CN.md +++ b/README-CN.md @@ -45,7 +45,7 @@ MQTT 代表 MQ Telemetry Transport。它是一种发布/订阅、非常简单和 - 通过所有 [Paho互操作性测试](https://github.com/eclipse/paho.mqtt.testing/tree/master/interoperability)(MQTT v5 和 MQTT v3)。 - 超过一千多个经过仔细考虑的单元测试场景。 - 支持 TCP、Websocket(包括 SSL/TLS)和$SYS 服务状态监控。 -- 内置 基于Redis、Badger 和 Bolt 的持久化(使用Hook钩子,你也可以自己创建)。 +- 内置 基于Redis、Badger、Pebble 和 Bolt 的持久化(使用Hook钩子,你也可以自己创建)。 - 内置基于规则的认证和 ACL 权限管理(使用Hook钩子,你也可以自己创建)。 ### 兼容性说明(Compatibility Notes) @@ -227,6 +227,7 @@ server := mqtt.New(&mqtt.Options{ | 访问控制 | [mochi-mqtt/server/hooks/auth . Auth](hooks/auth/auth.go) | 基于规则的访问权限控制。 | | 数据持久性 | [mochi-mqtt/server/hooks/storage/bolt](hooks/storage/bolt/bolt.go) | 使用 [BoltDB](https://dbdb.io/db/boltdb) 进行持久性存储(已弃用)。 | | 数据持久性 | [mochi-mqtt/server/hooks/storage/badger](hooks/storage/badger/badger.go) | 使用 [BadgerDB](https://github.com/dgraph-io/badger) 进行持久性存储。 | +| 数据持久性 | [mochi-mqtt/server/hooks/storage/pebble](hooks/storage/pebble/pebble.go) | 使用 [PebbleDB](https://github.com/cockroachdb/pebble) 进行持久性存储。 | | 数据持久性 | [mochi-mqtt/server/hooks/storage/redis](hooks/storage/redis/redis.go) | 使用 [Redis](https://redis.io) 进行持久性存储。 | | 调试跟踪 | [mochi-mqtt/server/hooks/debug](hooks/debug/debug.go) | 调试输出以查看数据包在服务端的链路追踪。 | @@ -329,9 +330,25 @@ if err != nil { ``` 有关 Redis 钩子的工作原理或如何使用它的更多信息,请参阅 [examples/persistence/redis/main.go](examples/persistence/redis/main.go) 或 [hooks/storage/redis](hooks/storage/redis) 。 +#### Pebble DB + +如果您更喜欢基于文件的存储,还有一个 PebbleDB 存储钩子(Hook)可用。它可以以与其他钩子大致相同的方式添加和配置(具有较少的选项)。 + +```go +err := server.AddHook(new(pebble.Hook), &pebble.Options{ + Path: pebblePath, + Mode: pebble.NoSync, +}) +if err != nil { + log.Fatal(err) +} +``` + +有关 pebble 钩子(Hook)的工作原理或如何使用它的更多信息,请参阅 [examples/persistence/pebble/main.go](examples/persistence/pebble/main.go) 或 [hooks/storage/pebble](hooks/storage/pebble)。 + #### Badger DB -如果您更喜欢基于文件的存储,还有一个 BadgerDB 存储钩子(Hook)可用。它可以以与其他钩子大致相同的方式添加和配置(具有较少的选项)。 +同样是基于文件的存储,还有一个 BadgerDB 存储钩子(Hook)可用。它可以以与其他钩子大致相同的方式添加和配置。 ```go err := server.AddHook(new(badger.Hook), &badger.Options{ diff --git a/README.md b/README.md index 09af17b9..47601d60 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ MQTT stands for [MQ Telemetry Transport](https://en.wikipedia.org/wiki/MQTT). It - Passes all [Paho Interoperability Tests](https://github.com/eclipse/paho.mqtt.testing/tree/master/interoperability) for MQTT v5 and MQTT v3. - Over a thousand carefully considered unit test scenarios. - TCP, Websocket (including SSL/TLS), and $SYS Dashboard listeners. -- Built-in Redis, Badger, and Bolt Persistence using Hooks (but you can also make your own). +- Built-in Redis, Badger, Pebble and Bolt Persistence using Hooks (but you can also make your own). - Built-in Rule-based Authentication and ACL Ledger using Hooks (also make your own). ### Compatibility Notes @@ -228,6 +228,7 @@ Hooks are stackable - you can add multiple hooks to a server, and they will be r | Access Control | [mochi-mqtt/server/hooks/auth . Auth](hooks/auth/auth.go) | Rule-based access control ledger. | | Persistence | [mochi-mqtt/server/hooks/storage/bolt](hooks/storage/bolt/bolt.go) | Persistent storage using [BoltDB](https://dbdb.io/db/boltdb) (deprecated). | | Persistence | [mochi-mqtt/server/hooks/storage/badger](hooks/storage/badger/badger.go) | Persistent storage using [BadgerDB](https://github.com/dgraph-io/badger). | +| Persistence | [mochi-mqtt/server/hooks/storage/pebble](hooks/storage/pebble/pebble.go) | Persistent storage using [PebbleDB](https://github.com/cockroachdb/pebble). | | Persistence | [mochi-mqtt/server/hooks/storage/redis](hooks/storage/redis/redis.go) | Persistent storage using [Redis](https://redis.io). | | Debugging | [mochi-mqtt/server/hooks/debug](hooks/debug/debug.go) | Additional debugging output to visualise packet flow. | @@ -322,8 +323,21 @@ if err != nil { ``` For more information on how the redis hook works, or how to use it, see the [examples/persistence/redis/main.go](examples/persistence/redis/main.go) or [hooks/storage/redis](hooks/storage/redis) code. +#### Pebble DB +There's also a Pebble Db storage hook if you prefer file-based storage. It can be added and configured in much the same way as the other hooks (with somewhat less options). +```go +err := server.AddHook(new(pebble.Hook), &pebble.Options{ + Path: pebblePath, + Mode: pebble.NoSync, +}) +if err != nil { + log.Fatal(err) +} +``` +For more information on how the pebble hook works, or how to use it, see the [examples/persistence/pebble/main.go](examples/persistence/pebble/main.go) or [hooks/storage/pebble](hooks/storage/pebble) code. + #### Badger DB -There's also a BadgerDB storage hook if you prefer file based storage. It can be added and configured in much the same way as the other hooks (with somewhat less options). +Similarly, for file-based storage, there is also a BadgerDB storage hook available. It can be added and configured in much the same way as the other hooks. ```go err := server.AddHook(new(badger.Hook), &badger.Options{ Path: badgerPath, diff --git a/config/config.go b/config/config.go index 5afe9dbb..f6860c1c 100644 --- a/config/config.go +++ b/config/config.go @@ -6,10 +6,12 @@ package config import ( "encoding/json" + "github.com/mochi-mqtt/server/v2/hooks/auth" "github.com/mochi-mqtt/server/v2/hooks/debug" "github.com/mochi-mqtt/server/v2/hooks/storage/badger" "github.com/mochi-mqtt/server/v2/hooks/storage/bolt" + "github.com/mochi-mqtt/server/v2/hooks/storage/pebble" "github.com/mochi-mqtt/server/v2/hooks/storage/redis" "github.com/mochi-mqtt/server/v2/listeners" "gopkg.in/yaml.v3" @@ -41,6 +43,7 @@ type HookAuthConfig struct { type HookStorageConfig struct { Badger *badger.Options `yaml:"badger" json:"badger"` Bolt *bolt.Options `yaml:"bolt" json:"bolt"` + Pebble *pebble.Options `yaml:"pebble" json:"pebble"` Redis *redis.Options `yaml:"redis" json:"redis"` } @@ -111,6 +114,13 @@ func (hc HookConfigs) toHooksStorage() []mqtt.HookLoadConfig { Config: hc.Storage.Redis, }) } + + if hc.Storage.Pebble != nil { + hlc = append(hlc, mqtt.HookLoadConfig{ + Hook: new(pebble.Hook), + Config: hc.Storage.Pebble, + }) + } return hlc } diff --git a/config/config_test.go b/config/config_test.go index 24d6f1a4..9531519e 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -12,6 +12,7 @@ import ( "github.com/mochi-mqtt/server/v2/hooks/auth" "github.com/mochi-mqtt/server/v2/hooks/storage/badger" "github.com/mochi-mqtt/server/v2/hooks/storage/bolt" + "github.com/mochi-mqtt/server/v2/hooks/storage/pebble" "github.com/mochi-mqtt/server/v2/hooks/storage/redis" "github.com/mochi-mqtt/server/v2/listeners" @@ -210,3 +211,23 @@ func TestToHooksStorageRedis(t *testing.T) { require.Equal(t, expect, th) } + +func TestToHooksStoragePebble(t *testing.T) { + hc := HookConfigs{ + Storage: &HookStorageConfig{ + Pebble: &pebble.Options{ + Path: "pebble", + }, + }, + } + + th := hc.toHooksStorage() + expect := []mqtt.HookLoadConfig{ + { + Hook: new(pebble.Hook), + Config: hc.Storage.Pebble, + }, + } + + require.Equal(t, expect, th) +} diff --git a/examples/config/config.json b/examples/config/config.json index 27a0bc93..f95a67ae 100644 --- a/examples/config/config.json +++ b/examples/config/config.json @@ -21,6 +21,10 @@ "enable": true }, "storage": { + "pebble": { + "path": "pebble.db", + "mode": "NoSync" + }, "badger": { "path": "badger.db", "gc_interval": 3, diff --git a/examples/config/config.yaml b/examples/config/config.yaml index 7a7332e3..4590d851 100644 --- a/examples/config/config.yaml +++ b/examples/config/config.yaml @@ -16,6 +16,9 @@ hooks: path: badger.db gc_interval: 3 gc_discard_ratio: 0.5 + pebble: + path: pebble.db + mode: "NoSync" bolt: path: bolt.db redis: diff --git a/hooks/storage/pebble/pebble.go b/hooks/storage/pebble/pebble.go index 7719e87e..b8feba0a 100644 --- a/hooks/storage/pebble/pebble.go +++ b/hooks/storage/pebble/pebble.go @@ -121,7 +121,7 @@ func (h *Hook) Init(config any) error { h.config = config.(*Options) } - if h.config.Path == "" { + if len(h.config.Path) == 0 { h.config.Path = defaultDbFile } @@ -487,29 +487,29 @@ func (h *Hook) Debugf(m string, v ...interface{}) { } // delKv deletes a key-value pair from the database. -func (h *Hook) delKv(key string) error { - err := h.db.Delete([]byte(key), h.mode) +func (h *Hook) delKv(k string) error { + err := h.db.Delete([]byte(k), h.mode) if err != nil { - h.Log.Error("failed to delete data", "error", err, "key", key) + h.Log.Error("failed to delete data", "error", err, "key", k) return err } return nil } // setKv stores a key-value pair in the database. -func (h *Hook) setKv(key string, obj storage.Serializable) error { - bs, _ := obj.MarshalBinary() - err := h.db.Set([]byte(key), bs, h.mode) +func (h *Hook) setKv(k string, v storage.Serializable) error { + bs, _ := v.MarshalBinary() + err := h.db.Set([]byte(k), bs, h.mode) if err != nil { - h.Log.Error("failed to update data", "error", err, "key", key) + h.Log.Error("failed to update data", "error", err, "key", k) return err } return nil } // getKv retrieves the value associated with a key from the database. -func (h *Hook) getKv(key string, obj storage.Serializable) error { - value, closer, err := h.db.Get([]byte(key)) +func (h *Hook) getKv(k string, v storage.Serializable) error { + value, closer, err := h.db.Get([]byte(k)) if err != nil { return err } @@ -519,5 +519,5 @@ func (h *Hook) getKv(key string, obj storage.Serializable) error { closer.Close() } }() - return obj.UnmarshalBinary(value) + return v.UnmarshalBinary(value) } From 5e81d39eb9bc837b6948278c429785519bfa2acd Mon Sep 17 00:00:00 2001 From: werbenhu Date: Wed, 27 Mar 2024 11:55:32 +0800 Subject: [PATCH 4/5] Resolve test failure issues and perform code optimization. --- hooks/storage/pebble/pebble.go | 7 ++++--- hooks/storage/pebble/pebble_test.go | 9 ++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/hooks/storage/pebble/pebble.go b/hooks/storage/pebble/pebble.go index b8feba0a..3e43b91b 100644 --- a/hooks/storage/pebble/pebble.go +++ b/hooks/storage/pebble/pebble.go @@ -459,10 +459,11 @@ func (h *Hook) StoredSysInfo() (v storage.SystemInfo, err error) { } err = h.getKv(sysInfoKey(), &v) - if err != nil && !errors.Is(err, pebbledb.ErrNotFound) { - return + if errors.Is(err, pebbledb.ErrNotFound) { + return v, nil } - return v, nil + + return } // Errorf satisfies the pebble interface for an error logger. diff --git a/hooks/storage/pebble/pebble_test.go b/hooks/storage/pebble/pebble_test.go index 22a0bea1..edd5f9a4 100644 --- a/hooks/storage/pebble/pebble_test.go +++ b/hooks/storage/pebble/pebble_test.go @@ -793,6 +793,9 @@ func TestStoredSysInfo(t *testing.T) { require.NoError(t, err) defer teardown(t, h.config.Path, h) + r, err := h.StoredSysInfo() + require.NoError(t, err) + // populate with messages err = h.setKv(storage.SysInfoKey, &storage.SystemInfo{ ID: storage.SysInfoKey, @@ -803,7 +806,7 @@ func TestStoredSysInfo(t *testing.T) { }) require.NoError(t, err) - r, err := h.StoredSysInfo() + r, err = h.StoredSysInfo() require.NoError(t, err) require.Equal(t, "2.0.0", r.Info.Version) }) @@ -846,7 +849,7 @@ func TestDebugf(t *testing.T) { h.Debugf("test", 1, 2, 3) } -func TestGetDelKv(t *testing.T) { +func TestGetSetDelKv(t *testing.T) { for _, tt := range opts { t.Run(tt.name, func(t *testing.T) { h := new(Hook) @@ -872,7 +875,7 @@ func TestGetDelKv(t *testing.T) { } } -func TestGetDelKvErr(t *testing.T) { +func TestGetSetDelKvErr(t *testing.T) { h := new(Hook) h.SetOpts(logger, nil) From 05c545ca927fe6455f75fc4614eaee9c66b494cd Mon Sep 17 00:00:00 2001 From: werbenhu Date: Wed, 27 Mar 2024 14:03:40 +0800 Subject: [PATCH 5/5] Optimized the test cases. --- hooks/storage/pebble/pebble_test.go | 804 ++++++++++++---------------- 1 file changed, 354 insertions(+), 450 deletions(-) diff --git a/hooks/storage/pebble/pebble_test.go b/hooks/storage/pebble/pebble_test.go index edd5f9a4..4a129853 100644 --- a/hooks/storage/pebble/pebble_test.go +++ b/hooks/storage/pebble/pebble_test.go @@ -35,24 +35,6 @@ var ( } pkf = packets.Packet{Filters: packets.Subscriptions{{Filter: "a/b/c"}}} - - opts = []struct { - name string - opt *Options - }{ - { - name: "NoSync", - opt: &Options{ - Mode: NoSync, - }, - }, - { - name: "Sync", - opt: &Options{ - Mode: Sync, - }, - }, - } ) func teardown(t *testing.T, path string, h *Hook) { @@ -159,68 +141,61 @@ func TestInitUseDefaults(t *testing.T) { } func TestOnSessionEstablishedThenOnDisconnect(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - defer teardown(t, h.config.Path, h) + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) - h.OnSessionEstablished(client, packets.Packet{}) + h.OnSessionEstablished(client, packets.Packet{}) - r := new(storage.Client) - err = h.getKv(clientKey(client), r) - require.NoError(t, err) - require.Equal(t, client.ID, r.ID) - require.Equal(t, client.Properties.Username, r.Username) - require.Equal(t, client.Properties.Clean, r.Clean) - require.Equal(t, client.Net.Remote, r.Remote) - require.Equal(t, client.Net.Listener, r.Listener) - require.NotSame(t, client, r) - - h.OnDisconnect(client, nil, false) - r2 := new(storage.Client) - err = h.getKv(clientKey(client), r2) - require.NoError(t, err) - require.Equal(t, client.ID, r.ID) + r := new(storage.Client) + err = h.getKv(clientKey(client), r) + require.NoError(t, err) + require.Equal(t, client.ID, r.ID) + require.Equal(t, client.Properties.Username, r.Username) + require.Equal(t, client.Properties.Clean, r.Clean) + require.Equal(t, client.Net.Remote, r.Remote) + require.Equal(t, client.Net.Listener, r.Listener) + require.NotSame(t, client, r) + + h.OnDisconnect(client, nil, false) + r2 := new(storage.Client) + err = h.getKv(clientKey(client), r2) + require.NoError(t, err) + require.Equal(t, client.ID, r.ID) + + h.OnDisconnect(client, nil, true) + r3 := new(storage.Client) + err = h.getKv(clientKey(client), r3) + require.Error(t, err) + require.ErrorIs(t, pebbledb.ErrNotFound, err) + require.Empty(t, r3.ID) - h.OnDisconnect(client, nil, true) - r3 := new(storage.Client) - err = h.getKv(clientKey(client), r3) - require.Error(t, err) - require.ErrorIs(t, pebbledb.ErrNotFound, err) - require.Empty(t, r3.ID) - }) - } } func TestOnClientExpired(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - defer teardown(t, h.config.Path, h) + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) - cl := &mqtt.Client{ID: "cl1"} - clientKey := clientKey(cl) + cl := &mqtt.Client{ID: "cl1"} + clientKey := clientKey(cl) - err = h.setKv(clientKey, &storage.Client{ID: cl.ID}) - require.NoError(t, err) + err = h.setKv(clientKey, &storage.Client{ID: cl.ID}) + require.NoError(t, err) - r := new(storage.Client) - err = h.getKv(clientKey, r) - require.NoError(t, err) - require.Equal(t, cl.ID, r.ID) + r := new(storage.Client) + err = h.getKv(clientKey, r) + require.NoError(t, err) + require.Equal(t, cl.ID, r.ID) - h.OnClientExpired(cl) - err = h.getKv(clientKey, r) - require.Error(t, err) - require.ErrorIs(t, err, pebbledb.ErrNotFound) - }) - } + h.OnClientExpired(cl) + err = h.getKv(clientKey, r) + require.Error(t, err) + require.ErrorIs(t, err, pebbledb.ErrNotFound) } func TestOnClientExpiredNoDB(t *testing.T) { @@ -230,16 +205,12 @@ func TestOnClientExpiredNoDB(t *testing.T) { } func TestOnClientExpiredClosedDB(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - teardown(t, h.config.Path, h) - h.OnClientExpired(client) - }) - } + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnClientExpired(client) } func TestOnSessionEstablishedNoDB(t *testing.T) { @@ -249,39 +220,31 @@ func TestOnSessionEstablishedNoDB(t *testing.T) { } func TestOnSessionEstablishedClosedDB(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - teardown(t, h.config.Path, h) - h.OnSessionEstablished(client, packets.Packet{}) - }) - } + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnSessionEstablished(client, packets.Packet{}) } func TestOnWillSent(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - defer teardown(t, h.config.Path, h) + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) - c1 := client - c1.Properties.Will.Flag = 1 - h.OnWillSent(c1, packets.Packet{}) + c1 := client + c1.Properties.Will.Flag = 1 + h.OnWillSent(c1, packets.Packet{}) - r := new(storage.Client) - err = h.getKv(clientKey(client), r) - require.NoError(t, err) + r := new(storage.Client) + err = h.getKv(clientKey(client), r) + require.NoError(t, err) - require.Equal(t, uint32(1), r.Will.Flag) - require.NotSame(t, client, r) - }) - } + require.Equal(t, uint32(1), r.Will.Flag) + require.NotSame(t, client, r) } func TestOnDisconnectNoDB(t *testing.T) { @@ -291,69 +254,57 @@ func TestOnDisconnectNoDB(t *testing.T) { } func TestOnDisconnectClosedDB(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - teardown(t, h.config.Path, h) - h.OnDisconnect(client, nil, false) - }) - } + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnDisconnect(client, nil, false) } func TestOnDisconnectSessionTakenOver(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) - testClient := &mqtt.Client{ - ID: "test", - Net: mqtt.ClientConnection{ - Remote: "test.addr", - Listener: "listener", - }, - Properties: mqtt.ClientProperties{ - Username: []byte("username"), - Clean: false, - }, - } - - testClient.Stop(packets.ErrSessionTakenOver) - h.OnDisconnect(testClient, nil, true) - teardown(t, h.config.Path, h) - }) + testClient := &mqtt.Client{ + ID: "test", + Net: mqtt.ClientConnection{ + Remote: "test.addr", + Listener: "listener", + }, + Properties: mqtt.ClientProperties{ + Username: []byte("username"), + Clean: false, + }, } + + testClient.Stop(packets.ErrSessionTakenOver) + h.OnDisconnect(testClient, nil, true) + teardown(t, h.config.Path, h) } func TestOnSubscribedThenOnUnsubscribed(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - defer teardown(t, h.config.Path, h) + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) - h.OnSubscribed(client, pkf, []byte{0}) - r := new(storage.Subscription) + h.OnSubscribed(client, pkf, []byte{0}) + r := new(storage.Subscription) - err = h.getKv(subscriptionKey(client, pkf.Filters[0].Filter), r) - require.NoError(t, err) - require.Equal(t, client.ID, r.Client) - require.Equal(t, pkf.Filters[0].Filter, r.Filter) - require.Equal(t, byte(0), r.Qos) + err = h.getKv(subscriptionKey(client, pkf.Filters[0].Filter), r) + require.NoError(t, err) + require.Equal(t, client.ID, r.Client) + require.Equal(t, pkf.Filters[0].Filter, r.Filter) + require.Equal(t, byte(0), r.Qos) - h.OnUnsubscribed(client, pkf) - err = h.getKv(subscriptionKey(client, pkf.Filters[0].Filter), r) - require.Error(t, err) - require.Equal(t, pebbledb.ErrNotFound, err) - }) - } + h.OnUnsubscribed(client, pkf) + err = h.getKv(subscriptionKey(client, pkf.Filters[0].Filter), r) + require.Error(t, err) + require.Equal(t, pebbledb.ErrNotFound, err) } func TestOnSubscribedNoDB(t *testing.T) { @@ -363,16 +314,12 @@ func TestOnSubscribedNoDB(t *testing.T) { } func TestOnSubscribedClosedDB(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - teardown(t, h.config.Path, h) - h.OnSubscribed(client, pkf, []byte{0}) - }) - } + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnSubscribed(client, pkf, []byte{0}) } func TestOnUnsubscribedNoDB(t *testing.T) { @@ -382,86 +329,74 @@ func TestOnUnsubscribedNoDB(t *testing.T) { } func TestOnUnsubscribedClosedDB(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - teardown(t, h.config.Path, h) - h.OnUnsubscribed(client, pkf) - }) - } + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnUnsubscribed(client, pkf) } func TestOnRetainMessageThenUnset(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - defer teardown(t, h.config.Path, h) + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) - pk := packets.Packet{ - FixedHeader: packets.FixedHeader{ - Retain: true, - }, - Payload: []byte("hello"), - TopicName: "a/b/c", - } + pk := packets.Packet{ + FixedHeader: packets.FixedHeader{ + Retain: true, + }, + Payload: []byte("hello"), + TopicName: "a/b/c", + } - h.OnRetainMessage(client, pk, 1) + h.OnRetainMessage(client, pk, 1) - r := new(storage.Message) - err = h.getKv(retainedKey(pk.TopicName), r) - require.NoError(t, err) - require.Equal(t, pk.TopicName, r.TopicName) - require.Equal(t, pk.Payload, r.Payload) + r := new(storage.Message) + err = h.getKv(retainedKey(pk.TopicName), r) + require.NoError(t, err) + require.Equal(t, pk.TopicName, r.TopicName) + require.Equal(t, pk.Payload, r.Payload) - h.OnRetainMessage(client, pk, -1) - err = h.getKv(retainedKey(pk.TopicName), r) - require.Error(t, err) - require.ErrorIs(t, err, pebbledb.ErrNotFound) + h.OnRetainMessage(client, pk, -1) + err = h.getKv(retainedKey(pk.TopicName), r) + require.Error(t, err) + require.ErrorIs(t, err, pebbledb.ErrNotFound) - // coverage: delete deleted - h.OnRetainMessage(client, pk, -1) - err = h.getKv(retainedKey(pk.TopicName), r) - require.Error(t, err) - require.ErrorIs(t, err, pebbledb.ErrNotFound) - }) - } + // coverage: delete deleted + h.OnRetainMessage(client, pk, -1) + err = h.getKv(retainedKey(pk.TopicName), r) + require.Error(t, err) + require.ErrorIs(t, err, pebbledb.ErrNotFound) } func TestOnRetainedExpired(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - defer teardown(t, h.config.Path, h) + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) - m := &storage.Message{ - ID: retainedKey("a/b/c"), - T: storage.RetainedKey, - TopicName: "a/b/c", - } + m := &storage.Message{ + ID: retainedKey("a/b/c"), + T: storage.RetainedKey, + TopicName: "a/b/c", + } - err = h.setKv(m.ID, m) - require.NoError(t, err) + err = h.setKv(m.ID, m) + require.NoError(t, err) - r := new(storage.Message) - err = h.getKv(m.ID, r) - require.NoError(t, err) - require.Equal(t, m.TopicName, r.TopicName) + r := new(storage.Message) + err = h.getKv(m.ID, r) + require.NoError(t, err) + require.Equal(t, m.TopicName, r.TopicName) - h.OnRetainedExpired(m.TopicName) - err = h.getKv(m.ID, r) - require.Error(t, err) - require.ErrorIs(t, err, pebbledb.ErrNotFound) - }) - } + h.OnRetainedExpired(m.TopicName) + err = h.getKv(m.ID, r) + require.Error(t, err) + require.ErrorIs(t, err, pebbledb.ErrNotFound) } func TestOnRetainExpiredNoDB(t *testing.T) { @@ -471,16 +406,12 @@ func TestOnRetainExpiredNoDB(t *testing.T) { } func TestOnRetainExpiredClosedDB(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - teardown(t, h.config.Path, h) - h.OnRetainedExpired("a/b/c") - }) - } + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnRetainedExpired("a/b/c") } func TestOnRetainMessageNoDB(t *testing.T) { @@ -490,55 +421,47 @@ func TestOnRetainMessageNoDB(t *testing.T) { } func TestOnRetainMessageClosedDB(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - teardown(t, h.config.Path, h) - h.OnRetainMessage(client, packets.Packet{}, 0) - }) - } + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnRetainMessage(client, packets.Packet{}, 0) } func TestOnQosPublishThenQOSComplete(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - defer teardown(t, h.config.Path, h) + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) - pk := packets.Packet{ - FixedHeader: packets.FixedHeader{ - Retain: true, - Qos: 2, - }, - Payload: []byte("hello"), - TopicName: "a/b/c", - } + pk := packets.Packet{ + FixedHeader: packets.FixedHeader{ + Retain: true, + Qos: 2, + }, + Payload: []byte("hello"), + TopicName: "a/b/c", + } - h.OnQosPublish(client, pk, time.Now().Unix(), 0) + h.OnQosPublish(client, pk, time.Now().Unix(), 0) - r := new(storage.Message) - err = h.getKv(inflightKey(client, pk), r) - require.NoError(t, err) - require.Equal(t, pk.TopicName, r.TopicName) - require.Equal(t, pk.Payload, r.Payload) + r := new(storage.Message) + err = h.getKv(inflightKey(client, pk), r) + require.NoError(t, err) + require.Equal(t, pk.TopicName, r.TopicName) + require.Equal(t, pk.Payload, r.Payload) - // ensure dates are properly saved - require.True(t, r.Sent > 0) - require.True(t, time.Now().Unix()-1 < r.Sent) + // ensure dates are properly saved + require.True(t, r.Sent > 0) + require.True(t, time.Now().Unix()-1 < r.Sent) - // OnQosDropped is a passthrough to OnQosComplete here - h.OnQosDropped(client, pk) - err = h.getKv(inflightKey(client, pk), r) - require.Error(t, err) - require.ErrorIs(t, err, pebbledb.ErrNotFound) - }) - } + // OnQosDropped is a passthrough to OnQosComplete here + h.OnQosDropped(client, pk) + err = h.getKv(inflightKey(client, pk), r) + require.Error(t, err) + require.ErrorIs(t, err, pebbledb.ErrNotFound) } func TestOnQosPublishNoDB(t *testing.T) { @@ -548,16 +471,12 @@ func TestOnQosPublishNoDB(t *testing.T) { } func TestOnQosPublishClosedDB(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - teardown(t, h.config.Path, h) - h.OnQosPublish(client, packets.Packet{}, time.Now().Unix(), 0) - }) - } + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnQosPublish(client, packets.Packet{}, time.Now().Unix(), 0) } func TestOnQosCompleteNoDB(t *testing.T) { @@ -567,16 +486,12 @@ func TestOnQosCompleteNoDB(t *testing.T) { } func TestOnQosCompleteClosedDB(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - teardown(t, h.config.Path, h) - h.OnQosComplete(client, packets.Packet{}) - }) - } + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnQosComplete(client, packets.Packet{}) } func TestOnQosDroppedNoDB(t *testing.T) { @@ -586,29 +501,25 @@ func TestOnQosDroppedNoDB(t *testing.T) { } func TestOnSysInfoTick(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - defer teardown(t, h.config.Path, h) + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) - info := &system.Info{ - Version: "2.0.0", - BytesReceived: 100, - } + info := &system.Info{ + Version: "2.0.0", + BytesReceived: 100, + } - h.OnSysInfoTick(info) + h.OnSysInfoTick(info) - r := new(storage.SystemInfo) - err = h.getKv(storage.SysInfoKey, r) - require.NoError(t, err) - require.Equal(t, info.Version, r.Version) - require.Equal(t, info.BytesReceived, r.BytesReceived) - require.NotSame(t, info, r) - }) - } + r := new(storage.SystemInfo) + err = h.getKv(storage.SysInfoKey, r) + require.NoError(t, err) + require.Equal(t, info.Version, r.Version) + require.Equal(t, info.BytesReceived, r.BytesReceived) + require.NotSame(t, info, r) } func TestOnSysInfoTickNoDB(t *testing.T) { @@ -618,45 +529,37 @@ func TestOnSysInfoTickNoDB(t *testing.T) { } func TestOnSysInfoTickClosedDB(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - teardown(t, h.config.Path, h) - h.OnSysInfoTick(new(system.Info)) - }) - } + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + teardown(t, h.config.Path, h) + h.OnSysInfoTick(new(system.Info)) } func TestStoredClients(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - defer teardown(t, h.config.Path, h) + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) - // populate with clients - err = h.setKv(storage.ClientKey+"_cl1", &storage.Client{ID: "cl1"}) - require.NoError(t, err) + // populate with clients + err = h.setKv(storage.ClientKey+"_cl1", &storage.Client{ID: "cl1"}) + require.NoError(t, err) - err = h.setKv(storage.ClientKey+"_cl2", &storage.Client{ID: "cl2"}) - require.NoError(t, err) + err = h.setKv(storage.ClientKey+"_cl2", &storage.Client{ID: "cl2"}) + require.NoError(t, err) - err = h.setKv(storage.ClientKey+"_cl3", &storage.Client{ID: "cl3"}) - require.NoError(t, err) + err = h.setKv(storage.ClientKey+"_cl3", &storage.Client{ID: "cl3"}) + require.NoError(t, err) - r, err := h.StoredClients() - require.NoError(t, err) - require.Len(t, r, 3) - require.Equal(t, "cl1", r[0].ID) - require.Equal(t, "cl2", r[1].ID) - require.Equal(t, "cl3", r[2].ID) - }) - } + r, err := h.StoredClients() + require.NoError(t, err) + require.Len(t, r, 3) + require.Equal(t, "cl1", r[0].ID) + require.Equal(t, "cl2", r[1].ID) + require.Equal(t, "cl3", r[2].ID) } func TestStoredClientsNoDB(t *testing.T) { @@ -668,32 +571,28 @@ func TestStoredClientsNoDB(t *testing.T) { } func TestStoredSubscriptions(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - defer teardown(t, h.config.Path, h) + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) - // populate with subscriptions - err = h.setKv(storage.SubscriptionKey+"_sub1", &storage.Subscription{ID: "sub1"}) - require.NoError(t, err) + // populate with subscriptions + err = h.setKv(storage.SubscriptionKey+"_sub1", &storage.Subscription{ID: "sub1"}) + require.NoError(t, err) - err = h.setKv(storage.SubscriptionKey+"_sub2", &storage.Subscription{ID: "sub2"}) - require.NoError(t, err) + err = h.setKv(storage.SubscriptionKey+"_sub2", &storage.Subscription{ID: "sub2"}) + require.NoError(t, err) - err = h.setKv(storage.SubscriptionKey+"_sub3", &storage.Subscription{ID: "sub3"}) - require.NoError(t, err) + err = h.setKv(storage.SubscriptionKey+"_sub3", &storage.Subscription{ID: "sub3"}) + require.NoError(t, err) - r, err := h.StoredSubscriptions() - require.NoError(t, err) - require.Len(t, r, 3) - require.Equal(t, "sub1", r[0].ID) - require.Equal(t, "sub2", r[1].ID) - require.Equal(t, "sub3", r[2].ID) - }) - } + r, err := h.StoredSubscriptions() + require.NoError(t, err) + require.Len(t, r, 3) + require.Equal(t, "sub1", r[0].ID) + require.Equal(t, "sub2", r[1].ID) + require.Equal(t, "sub3", r[2].ID) } func TestStoredSubscriptionsNoDB(t *testing.T) { @@ -705,35 +604,31 @@ func TestStoredSubscriptionsNoDB(t *testing.T) { } func TestStoredRetainedMessages(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - defer teardown(t, h.config.Path, h) + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) - // populate with messages - err = h.setKv(storage.RetainedKey+"_m1", &storage.Message{ID: "m1"}) - require.NoError(t, err) + // populate with messages + err = h.setKv(storage.RetainedKey+"_m1", &storage.Message{ID: "m1"}) + require.NoError(t, err) - err = h.setKv(storage.RetainedKey+"_m2", &storage.Message{ID: "m2"}) - require.NoError(t, err) + err = h.setKv(storage.RetainedKey+"_m2", &storage.Message{ID: "m2"}) + require.NoError(t, err) - err = h.setKv(storage.RetainedKey+"_m3", &storage.Message{ID: "m3"}) - require.NoError(t, err) + err = h.setKv(storage.RetainedKey+"_m3", &storage.Message{ID: "m3"}) + require.NoError(t, err) - err = h.setKv(storage.InflightKey+"_i3", &storage.Message{ID: "i3"}) - require.NoError(t, err) + err = h.setKv(storage.InflightKey+"_i3", &storage.Message{ID: "i3"}) + require.NoError(t, err) - r, err := h.StoredRetainedMessages() - require.NoError(t, err) - require.Len(t, r, 3) - require.Equal(t, "m1", r[0].ID) - require.Equal(t, "m2", r[1].ID) - require.Equal(t, "m3", r[2].ID) - }) - } + r, err := h.StoredRetainedMessages() + require.NoError(t, err) + require.Len(t, r, 3) + require.Equal(t, "m1", r[0].ID) + require.Equal(t, "m2", r[1].ID) + require.Equal(t, "m3", r[2].ID) } func TestStoredRetainedMessagesNoDB(t *testing.T) { @@ -745,35 +640,31 @@ func TestStoredRetainedMessagesNoDB(t *testing.T) { } func TestStoredInflightMessages(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - defer teardown(t, h.config.Path, h) + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) - // populate with messages - err = h.setKv(storage.InflightKey+"_i1", &storage.Message{ID: "i1"}) - require.NoError(t, err) + // populate with messages + err = h.setKv(storage.InflightKey+"_i1", &storage.Message{ID: "i1"}) + require.NoError(t, err) - err = h.setKv(storage.InflightKey+"_i2", &storage.Message{ID: "i2"}) - require.NoError(t, err) + err = h.setKv(storage.InflightKey+"_i2", &storage.Message{ID: "i2"}) + require.NoError(t, err) - err = h.setKv(storage.InflightKey+"_i3", &storage.Message{ID: "i3"}) - require.NoError(t, err) + err = h.setKv(storage.InflightKey+"_i3", &storage.Message{ID: "i3"}) + require.NoError(t, err) - err = h.setKv(storage.RetainedKey+"_m1", &storage.Message{ID: "m1"}) - require.NoError(t, err) + err = h.setKv(storage.RetainedKey+"_m1", &storage.Message{ID: "m1"}) + require.NoError(t, err) - r, err := h.StoredInflightMessages() - require.NoError(t, err) - require.Len(t, r, 3) - require.Equal(t, "i1", r[0].ID) - require.Equal(t, "i2", r[1].ID) - require.Equal(t, "i3", r[2].ID) - }) - } + r, err := h.StoredInflightMessages() + require.NoError(t, err) + require.Len(t, r, 3) + require.Equal(t, "i1", r[0].ID) + require.Equal(t, "i2", r[1].ID) + require.Equal(t, "i3", r[2].ID) } func TestStoredInflightMessagesNoDB(t *testing.T) { @@ -785,32 +676,28 @@ func TestStoredInflightMessagesNoDB(t *testing.T) { } func TestStoredSysInfo(t *testing.T) { - for _, tt := range opts { - t.Run(tt.name, func(t *testing.T) { - h := new(Hook) - h.SetOpts(logger, nil) - err := h.Init(tt.opt) - require.NoError(t, err) - defer teardown(t, h.config.Path, h) + h := new(Hook) + h.SetOpts(logger, nil) + err := h.Init(nil) + require.NoError(t, err) + defer teardown(t, h.config.Path, h) - r, err := h.StoredSysInfo() - require.NoError(t, err) + r, err := h.StoredSysInfo() + require.NoError(t, err) - // populate with messages - err = h.setKv(storage.SysInfoKey, &storage.SystemInfo{ - ID: storage.SysInfoKey, - Info: system.Info{ - Version: "2.0.0", - }, - T: storage.SysInfoKey, - }) - require.NoError(t, err) + // populate with messages + err = h.setKv(storage.SysInfoKey, &storage.SystemInfo{ + ID: storage.SysInfoKey, + Info: system.Info{ + Version: "2.0.0", + }, + T: storage.SysInfoKey, + }) + require.NoError(t, err) - r, err = h.StoredSysInfo() - require.NoError(t, err) - require.Equal(t, "2.0.0", r.Info.Version) - }) - } + r, err = h.StoredSysInfo() + require.NoError(t, err) + require.Equal(t, "2.0.0", r.Info.Version) } func TestStoredSysInfoNoDB(t *testing.T) { @@ -850,6 +737,23 @@ func TestDebugf(t *testing.T) { } func TestGetSetDelKv(t *testing.T) { + opts := []struct { + name string + opt *Options + }{ + { + name: "NoSync", + opt: &Options{ + Mode: NoSync, + }, + }, + { + name: "Sync", + opt: &Options{ + Mode: Sync, + }, + }, + } for _, tt := range opts { t.Run(tt.name, func(t *testing.T) { h := new(Hook)