This commit is contained in:
Кобелев Андрей Андреевич
2022-08-15 23:06:20 +03:00
commit 4ea1a3802b
532 changed files with 211522 additions and 0 deletions

View File

@ -0,0 +1,266 @@
package bolt
import (
"fmt"
"time"
sgob "github.com/asdine/storm/codec/gob"
"github.com/asdine/storm/v3"
"go.etcd.io/bbolt"
"github.com/mochi-co/mqtt/server/persistence"
)
const (
// defaultPath is the default file to use to store the data.
defaultPath = "mochi.db"
// defaultTimeout is the default timeout of the file lock.
defaultTimeout = 250 * time.Millisecond
)
var (
// ErrDBNotOpen indicates the bolt db file is not open for reading.
ErrDBNotOpen = fmt.Errorf("boltdb not opened")
)
// Store is a backend for writing and reading to bolt persistent storage.
type Store struct {
path string // the path on which to store the db file.
opts *bbolt.Options // options for configuring the boltdb instance.
db *storm.DB // the boltdb instance.
}
// New returns a configured instance of the boltdb store.
func New(path string, opts *bbolt.Options) *Store {
if path == "" || path == "." {
path = defaultPath
}
if opts == nil {
opts = &bbolt.Options{
Timeout: defaultTimeout,
}
}
return &Store{
path: path,
opts: opts,
}
}
// Open opens the boltdb instance.
func (s *Store) Open() error {
var err error
s.db, err = storm.Open(s.path, storm.BoltOptions(0600, s.opts), storm.Codec(sgob.Codec))
if err != nil {
return err
}
return nil
}
// Close closes the boltdb instance.
func (s *Store) Close() {
s.db.Close()
}
// WriteServerInfo writes the server info to the boltdb instance.
func (s *Store) WriteServerInfo(v persistence.ServerInfo) error {
if s.db == nil {
return ErrDBNotOpen
}
err := s.db.Save(&v)
if err != nil {
return err
}
return nil
}
// WriteSubscription writes a single subscription to the boltdb instance.
func (s *Store) WriteSubscription(v persistence.Subscription) error {
if s.db == nil {
return ErrDBNotOpen
}
err := s.db.Save(&v)
if err != nil {
return err
}
return nil
}
// WriteInflight writes a single inflight message to the boltdb instance.
func (s *Store) WriteInflight(v persistence.Message) error {
if s.db == nil {
return ErrDBNotOpen
}
err := s.db.Save(&v)
if err != nil {
return err
}
return nil
}
// WriteRetained writes a single retained message to the boltdb instance.
func (s *Store) WriteRetained(v persistence.Message) error {
if s.db == nil {
return ErrDBNotOpen
}
err := s.db.Save(&v)
if err != nil {
return err
}
return nil
}
// WriteClient writes a single client to the boltdb instance.
func (s *Store) WriteClient(v persistence.Client) error {
if s.db == nil {
return ErrDBNotOpen
}
err := s.db.Save(&v)
if err != nil {
return err
}
return nil
}
// DeleteSubscription deletes a subscription from the boltdb instance.
func (s *Store) DeleteSubscription(id string) error {
if s.db == nil {
return ErrDBNotOpen
}
err := s.db.DeleteStruct(&persistence.Subscription{
ID: id,
})
if err != nil {
return err
}
return nil
}
// DeleteClient deletes a client from the boltdb instance.
func (s *Store) DeleteClient(id string) error {
if s.db == nil {
return ErrDBNotOpen
}
err := s.db.DeleteStruct(&persistence.Client{
ID: id,
})
if err != nil {
return err
}
return nil
}
// DeleteInflight deletes an inflight message from the boltdb instance.
func (s *Store) DeleteInflight(id string) error {
if s.db == nil {
return ErrDBNotOpen
}
err := s.db.DeleteStruct(&persistence.Message{
ID: id,
})
if err != nil {
return err
}
return nil
}
// DeleteRetained deletes a retained message from the boltdb instance.
func (s *Store) DeleteRetained(id string) error {
if s.db == nil {
return ErrDBNotOpen
}
err := s.db.DeleteStruct(&persistence.Message{
ID: id,
})
if err != nil {
return err
}
return nil
}
// ReadSubscriptions loads all the subscriptions from the boltdb instance.
func (s *Store) ReadSubscriptions() (v []persistence.Subscription, err error) {
if s.db == nil {
return v, ErrDBNotOpen
}
err = s.db.Find("T", persistence.KSubscription, &v)
if err != nil && err != storm.ErrNotFound {
return
}
return v, nil
}
// ReadClients loads all the clients from the boltdb instance.
func (s *Store) ReadClients() (v []persistence.Client, err error) {
if s.db == nil {
return v, ErrDBNotOpen
}
err = s.db.Find("T", persistence.KClient, &v)
if err != nil && err != storm.ErrNotFound {
return
}
return v, nil
}
// ReadInflight loads all the inflight messages from the boltdb instance.
func (s *Store) ReadInflight() (v []persistence.Message, err error) {
if s.db == nil {
return v, ErrDBNotOpen
}
err = s.db.Find("T", persistence.KInflight, &v)
if err != nil && err != storm.ErrNotFound {
return
}
return v, nil
}
// ReadRetained loads all the retained messages from the boltdb instance.
func (s *Store) ReadRetained() (v []persistence.Message, err error) {
if s.db == nil {
return v, ErrDBNotOpen
}
err = s.db.Find("T", persistence.KRetained, &v)
if err != nil && err != storm.ErrNotFound {
return
}
return v, nil
}
//ReadServerInfo loads the server info from the boltdb instance.
func (s *Store) ReadServerInfo() (v persistence.ServerInfo, err error) {
if s.db == nil {
return v, ErrDBNotOpen
}
err = s.db.One("ID", persistence.KServerInfo, &v)
if err != nil && err != storm.ErrNotFound {
return
}
return v, nil
}

View File

@ -0,0 +1,486 @@
package bolt
import (
"os"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.etcd.io/bbolt"
"github.com/mochi-co/mqtt/server/persistence"
"github.com/mochi-co/mqtt/server/system"
)
const tmpPath = "testbolt.db"
func teardown(s *Store, t *testing.T) {
s.Close()
err := os.Remove(tmpPath)
require.NoError(t, err)
}
func TestSatsifies(t *testing.T) {
var x persistence.Store
x = New(tmpPath, &bbolt.Options{
Timeout: 500 * time.Millisecond,
})
require.NotNil(t, x)
}
func TestNew(t *testing.T) {
s := New(tmpPath, &bbolt.Options{
Timeout: 500 * time.Millisecond,
})
require.NotNil(t, s)
require.Equal(t, tmpPath, s.path)
require.Equal(t, 500*time.Millisecond, s.opts.Timeout)
}
func TestNewNoPath(t *testing.T) {
s := New("", nil)
require.NotNil(t, s)
require.Equal(t, defaultPath, s.path)
}
func TestNewNoOpts(t *testing.T) {
s := New("", nil)
require.NotNil(t, s)
require.Equal(t, defaultTimeout, s.opts.Timeout)
}
func TestOpen(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
defer teardown(s, t)
require.NotNil(t, s.db)
}
func TestOpenFailure(t *testing.T) {
s := New("..", nil)
err := s.Open()
require.Error(t, err)
}
func TestWriteAndRetrieveServerInfo(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
defer teardown(s, t)
v := system.Info{
Version: "test",
Started: 100,
}
err = s.WriteServerInfo(persistence.ServerInfo{
Info: v,
ID: persistence.KServerInfo,
})
require.NoError(t, err)
r, err := s.ReadServerInfo()
require.NoError(t, err)
require.NotNil(t, r)
require.Equal(t, v.Version, r.Version)
require.Equal(t, v.Started, r.Started)
}
func TestWriteServerInfoNoDB(t *testing.T) {
s := New(tmpPath, nil)
err := s.WriteServerInfo(persistence.ServerInfo{})
require.Error(t, err)
}
func TestWriteServerInfoFail(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
err = os.Remove(tmpPath)
require.NoError(t, err)
err = s.WriteServerInfo(persistence.ServerInfo{})
require.Error(t, err)
}
func TestReadServerInfoNoDB(t *testing.T) {
s := New(tmpPath, nil)
_, err := s.ReadServerInfo()
require.Error(t, err)
}
func TestReadServerInfoFail(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
s.Close()
_, err = s.ReadServerInfo()
require.Error(t, err)
}
func TestWriteRetrieveDeleteSubscription(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
defer teardown(s, t)
v := persistence.Subscription{
ID: "test:a/b/c",
Client: "test",
Filter: "a/b/c",
QoS: 1,
T: persistence.KSubscription,
}
err = s.WriteSubscription(v)
require.NoError(t, err)
v2 := persistence.Subscription{
ID: "test:d/e/f",
Client: "test",
Filter: "d/e/f",
QoS: 2,
T: persistence.KSubscription,
}
err = s.WriteSubscription(v2)
require.NoError(t, err)
subs, err := s.ReadSubscriptions()
require.NoError(t, err)
require.Equal(t, persistence.KSubscription, subs[0].T)
require.Equal(t, 2, len(subs))
err = s.DeleteSubscription("test:d/e/f")
require.NoError(t, err)
subs, err = s.ReadSubscriptions()
require.NoError(t, err)
require.Equal(t, 1, len(subs))
}
func TestWriteSubscriptionNoDB(t *testing.T) {
s := New(tmpPath, nil)
err := s.WriteSubscription(persistence.Subscription{})
require.Error(t, err)
}
func TestWriteSubscriptionFail(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
s.Close()
err = s.WriteSubscription(persistence.Subscription{})
require.Error(t, err)
}
func TestReadSubscriptionNoDB(t *testing.T) {
s := New(tmpPath, nil)
_, err := s.ReadSubscriptions()
require.Error(t, err)
}
func TestReadSubscriptionFail(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
s.Close()
_, err = s.ReadSubscriptions()
require.Error(t, err)
}
func TestWriteRetrieveDeleteInflight(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
defer teardown(s, t)
v := persistence.Message{
ID: "client1_if_0",
T: persistence.KInflight,
PacketID: 0,
TopicName: "a/b/c",
Payload: []byte{'h', 'e', 'l', 'l', 'o'},
Sent: 100,
Resends: 0,
}
err = s.WriteInflight(v)
require.NoError(t, err)
v2 := persistence.Message{
ID: "client1_if_100",
T: persistence.KInflight,
PacketID: 100,
TopicName: "d/e/f",
Payload: []byte{'y', 'e', 's'},
Sent: 200,
Resends: 1,
}
err = s.WriteInflight(v2)
require.NoError(t, err)
msgs, err := s.ReadInflight()
require.NoError(t, err)
require.Equal(t, persistence.KInflight, msgs[0].T)
require.Equal(t, 2, len(msgs))
err = s.DeleteInflight("client1_if_100")
require.NoError(t, err)
msgs, err = s.ReadInflight()
require.NoError(t, err)
require.Equal(t, 1, len(msgs))
}
func TestWriteInflightNoDB(t *testing.T) {
s := New(tmpPath, nil)
err := s.WriteInflight(persistence.Message{})
require.Error(t, err)
}
func TestWriteInflightFail(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
s.Close()
err = s.WriteInflight(persistence.Message{})
require.Error(t, err)
}
func TestReadInflightNoDB(t *testing.T) {
s := New(tmpPath, nil)
_, err := s.ReadInflight()
require.Error(t, err)
}
func TestReadInflightFail(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
s.Close()
_, err = s.ReadInflight()
require.Error(t, err)
}
func TestWriteRetrieveDeleteRetained(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
defer teardown(s, t)
v := persistence.Message{
ID: "client1_ret_200",
T: persistence.KRetained,
FixedHeader: persistence.FixedHeader{
Retain: true,
},
PacketID: 200,
TopicName: "a/b/c",
Payload: []byte{'h', 'e', 'l', 'l', 'o'},
Sent: 100,
Resends: 0,
}
err = s.WriteRetained(v)
require.NoError(t, err)
v2 := persistence.Message{
ID: "client1_ret_300",
T: persistence.KRetained,
FixedHeader: persistence.FixedHeader{
Retain: true,
},
PacketID: 100,
TopicName: "d/e/f",
Payload: []byte{'y', 'e', 's'},
Sent: 200,
Resends: 1,
}
err = s.WriteRetained(v2)
require.NoError(t, err)
msgs, err := s.ReadRetained()
require.NoError(t, err)
require.Equal(t, persistence.KRetained, msgs[0].T)
require.Equal(t, true, msgs[0].FixedHeader.Retain)
require.Equal(t, 2, len(msgs))
err = s.DeleteRetained("client1_ret_300")
require.NoError(t, err)
msgs, err = s.ReadRetained()
require.NoError(t, err)
require.Equal(t, 1, len(msgs))
}
func TestWriteRetainedNoDB(t *testing.T) {
s := New(tmpPath, nil)
err := s.WriteRetained(persistence.Message{})
require.Error(t, err)
}
func TestWriteRetainedFail(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
err = os.Remove(tmpPath)
require.NoError(t, err)
err = s.WriteRetained(persistence.Message{})
require.Error(t, err)
}
func TestReadRetainedNoDB(t *testing.T) {
s := New(tmpPath, nil)
_, err := s.ReadRetained()
require.Error(t, err)
}
func TestReadRetainedFail(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
s.Close()
_, err = s.ReadRetained()
require.Error(t, err)
}
func TestWriteRetrieveDeleteClients(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
defer teardown(s, t)
v := persistence.Client{
ID: "cl_client1",
ClientID: "client1",
T: persistence.KClient,
Listener: "tcp1",
Username: []byte{'m', 'o', 'c', 'h', 'i'},
LWT: persistence.LWT{
Topic: "a/b/c",
Message: []byte{'h', 'e', 'l', 'l', 'o'},
Qos: 1,
Retain: true,
},
}
err = s.WriteClient(v)
require.NoError(t, err)
clients, err := s.ReadClients()
require.NoError(t, err)
require.Equal(t, []byte{'m', 'o', 'c', 'h', 'i'}, clients[0].Username)
require.Equal(t, "a/b/c", clients[0].LWT.Topic)
v2 := persistence.Client{
ID: "cl_client2",
ClientID: "client2",
T: persistence.KClient,
Listener: "tcp1",
}
err = s.WriteClient(v2)
require.NoError(t, err)
clients, err = s.ReadClients()
require.NoError(t, err)
require.Equal(t, persistence.KClient, clients[0].T)
require.Equal(t, 2, len(clients))
err = s.DeleteClient("cl_client2")
require.NoError(t, err)
clients, err = s.ReadClients()
require.NoError(t, err)
require.Equal(t, 1, len(clients))
}
func TestWriteClientNoDB(t *testing.T) {
s := New(tmpPath, nil)
err := s.WriteClient(persistence.Client{})
require.Error(t, err)
}
func TestWriteClientFail(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
s.Close()
err = s.WriteClient(persistence.Client{})
require.Error(t, err)
}
func TestReadClientNoDB(t *testing.T) {
s := New(tmpPath, nil)
_, err := s.ReadClients()
require.Error(t, err)
}
func TestReadClientFail(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
s.Close()
_, err = s.ReadClients()
require.Error(t, err)
}
func TestDeleteSubscriptionNoDB(t *testing.T) {
s := New(tmpPath, nil)
err := s.DeleteSubscription("a")
require.Error(t, err)
}
func TestDeleteSubscriptionFail(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
s.Close()
err = s.DeleteSubscription("a")
require.Error(t, err)
}
func TestDeleteClientNoDB(t *testing.T) {
s := New(tmpPath, nil)
err := s.DeleteClient("a")
require.Error(t, err)
}
func TestDeleteClientFail(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
s.Close()
err = s.DeleteClient("a")
require.Error(t, err)
}
func TestDeleteInflightNoDB(t *testing.T) {
s := New(tmpPath, nil)
err := s.DeleteInflight("a")
require.Error(t, err)
}
func TestDeleteInflightFail(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
s.Close()
err = s.DeleteInflight("a")
require.Error(t, err)
}
func TestDeleteRetainedNoDB(t *testing.T) {
s := New(tmpPath, nil)
err := s.DeleteRetained("a")
require.Error(t, err)
}
func TestDeleteRetainedFail(t *testing.T) {
s := New(tmpPath, nil)
err := s.Open()
require.NoError(t, err)
s.Close()
err = s.DeleteRetained("a")
require.Error(t, err)
}