Skip to content

Commit 4418034

Browse files
authored
Adds the new history API (#414)
1 parent 374de74 commit 4418034

File tree

13 files changed

+305
-33
lines changed

13 files changed

+305
-33
lines changed

internal/broker/service.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
"github.com/emitter-io/emitter/internal/security"
4444
"github.com/emitter-io/emitter/internal/security/license"
4545
"github.com/emitter-io/emitter/internal/service/cluster"
46+
"github.com/emitter-io/emitter/internal/service/history"
4647
"github.com/emitter-io/emitter/internal/service/keyban"
4748
"github.com/emitter-io/emitter/internal/service/keygen"
4849
"github.com/emitter-io/emitter/internal/service/link"
@@ -189,6 +190,7 @@ func NewService(ctx context.Context, cfg *config.Config) (s *Service, err error)
189190
s.pubsub.Handle("keyban", keyban.New(s, s.keygen, s.cluster).OnRequest)
190191
s.pubsub.Handle("link", link.New(s, s.pubsub).OnRequest)
191192
s.pubsub.Handle("me", me.New().OnRequest)
193+
s.pubsub.Handle("history", history.New(s, s.storage).OnRequest)
192194

193195
// Addresses and things
194196
logging.LogTarget("service", "configured node name", nodeName)

internal/provider/storage/memory_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ func TestInMemory_Query(t *testing.T) {
122122
})
123123
}
124124

125-
out, err := s.Query(tc.query, zero, zero, tc.limit)
125+
out, err := s.Query(tc.query, zero, zero, nil, tc.limit)
126126
assert.NoError(t, err)
127127

128128
count := 0
@@ -152,7 +152,7 @@ func TestInMemory_lookup(t *testing.T) {
152152
}
153153

154154
for _, tc := range tests {
155-
matches := s.lookup(newLookupQuery(tc.query, zero, zero, tc.limit))
155+
matches := s.lookup(newLookupQuery(tc.query, zero, zero, nil, tc.limit))
156156
assert.Equal(t, tc.count, len(matches))
157157
}
158158
}
@@ -172,13 +172,13 @@ func TestInMemory_OnSurvey(t *testing.T) {
172172
{name: "ssdstore"},
173173
{
174174
name: "ssdstore",
175-
query: newLookupQuery(message.Ssid{0, 1}, zero, zero, 1),
175+
query: newLookupQuery(message.Ssid{0, 1}, zero, zero, nil, 1),
176176
expectOk: true,
177177
expectCount: 1,
178178
},
179179
{
180180
name: "ssdstore",
181-
query: newLookupQuery(message.Ssid{0, 1}, zero, zero, 10),
181+
query: newLookupQuery(message.Ssid{0, 1}, zero, zero, nil, 10),
182182
expectOk: true,
183183
expectCount: 2,
184184
},

internal/provider/storage/ssd.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,10 @@ func encodeFrame(msgs message.Frame) []*badger.Entry {
125125
// Query performs a query and attempts to fetch last n messages where
126126
// n is specified by limit argument. From and until times can also be specified
127127
// for time-series retrieval.
128-
func (s *SSD) Query(ssid message.Ssid, from, until time.Time, limit int) (message.Frame, error) {
128+
func (s *SSD) Query(ssid message.Ssid, from, until time.Time, startFromID message.ID, limit int) (message.Frame, error) {
129129

130130
// Construct a query and lookup locally first
131-
query := newLookupQuery(ssid, from, until, limit)
131+
query := newLookupQuery(ssid, from, until, startFromID, limit)
132132
match := s.lookup(query)
133133

134134
// Issue the message survey to the cluster
@@ -184,11 +184,21 @@ func (s *SSD) lookup(q lookupQuery) (matches message.Frame) {
184184

185185
// Since we're starting backwards, seek to the 'until' position first and then
186186
// we'll iterate forward but have reverse time ('until' -> 'from')
187-
prefix := message.NewPrefix(q.Ssid, q.Until)
187+
var prefix message.ID
188+
if len(q.StartFromID) == 0 {
189+
prefix = message.NewPrefix(q.Ssid, q.Until)
190+
it.Seek(prefix)
191+
} else {
192+
it.Seek(q.StartFromID)
193+
if !it.Valid() {
194+
return nil
195+
}
196+
it.Next()
197+
}
188198

189199
matchesSize := 0
190200
// Seek the prefix and check the key so we can quickly exit the iteration.
191-
for it.Seek(prefix); it.Valid() &&
201+
for ; it.Valid() &&
192202
message.ID(it.Item().Key()).HasPrefix(q.Ssid, q.From) &&
193203
len(matches) < q.Limit; it.Next() {
194204
if !message.ID(it.Item().Key()).Match(q.Ssid, q.From, q.Until) {

internal/provider/storage/ssd_test.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func TestSSD_Query(t *testing.T) {
7171
assert.NoError(t, err)
7272

7373
zero := time.Unix(0, 0)
74-
f, err := store.Query([]uint32{0, 3, 2, 6}, zero, zero, 5)
74+
f, err := store.Query([]uint32{0, 3, 2, 6}, zero, zero, nil, 5)
7575
assert.NoError(t, err)
7676
assert.Len(t, f, 1)
7777
})
@@ -83,6 +83,12 @@ func TestSSD_QueryOrdered(t *testing.T) {
8383
})
8484
}
8585

86+
func TestSSD_QueryStartFromID(t *testing.T) {
87+
runSSDTest(func(store *SSD) {
88+
testStartFromID(t, store)
89+
})
90+
}
91+
8692
func TestSSD_MaxResponseSizeReached(t *testing.T) {
8793
runSSDTest(func(store *SSD) {
8894
testMaxResponseSizeReached(t, store)
@@ -127,7 +133,7 @@ func TestSSD_QuerySurveyed(t *testing.T) {
127133
})
128134
}
129135

130-
out, err := s.Query(tc.query, zero, zero, tc.limit)
136+
out, err := s.Query(tc.query, zero, zero, nil, tc.limit)
131137
assert.NoError(t, err)
132138
count := 0
133139
for range out {
@@ -152,13 +158,13 @@ func TestSSD_OnSurvey(t *testing.T) {
152158
{name: "ssdstore"},
153159
{
154160
name: "ssdstore",
155-
query: newLookupQuery(message.Ssid{0, 1}, zero, zero, 1),
161+
query: newLookupQuery(message.Ssid{0, 1}, zero, zero, nil, 1),
156162
expectOk: true,
157163
expectCount: 1,
158164
},
159165
{
160166
name: "ssdstore",
161-
query: newLookupQuery(message.Ssid{0, 1}, zero, zero, 10),
167+
query: newLookupQuery(message.Ssid{0, 1}, zero, zero, nil, 10),
162168
expectOk: true,
163169
expectCount: 2,
164170
},
@@ -322,7 +328,7 @@ func benchmarkQuery(b *testing.B, store *SSD, last int, m *stats.Metric) {
322328
return
323329

324330
default:
325-
store.Query(ssid, t0, t1, last)
331+
store.Query(ssid, t0, t1, nil, last)
326332
m.Update(int32(last))
327333
}
328334
}

internal/provider/storage/storage.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ type Storage interface {
4747
// Query performs a query and attempts to fetch last n messages where
4848
// n is specified by limit argument. From and until times can also be specified
4949
// for time-series retrieval.
50-
Query(ssid message.Ssid, from, until time.Time, limit int) (message.Frame, error)
50+
Query(ssid message.Ssid, from, until time.Time, startFromID message.ID, limit int) (message.Frame, error)
5151
}
5252

5353
// ------------------------------------------------------------------------------------
@@ -65,20 +65,22 @@ func window(from, until time.Time) (int64, int64) {
6565

6666
// The lookup query to send out to the cluster.
6767
type lookupQuery struct {
68-
Ssid message.Ssid // The ssid to match.
69-
From int64 // The beginning of the time window.
70-
Until int64 // The end of the time window.
71-
Limit int // The maximum number of elements to return.
68+
Ssid message.Ssid // The ssid to match.
69+
From int64 // The beginning of the time window.
70+
Until int64 // The end of the time window.
71+
StartFromID message.ID // The ID to start from when retrieving message, used for pagination.
72+
Limit int // The maximum number of elements to return.
7273
}
7374

7475
// newLookupQuery creates a new lookup query
75-
func newLookupQuery(ssid message.Ssid, from, until time.Time, limit int) lookupQuery {
76+
func newLookupQuery(ssid message.Ssid, from, until time.Time, startFromID message.ID, limit int) lookupQuery {
7677
t0, t1 := window(from, until)
7778
return lookupQuery{
78-
Ssid: ssid,
79-
From: t0,
80-
Until: t1,
81-
Limit: limit,
79+
Ssid: ssid,
80+
From: t0,
81+
Until: t1,
82+
StartFromID: startFromID,
83+
Limit: limit,
8284
}
8385
}
8486

@@ -128,7 +130,7 @@ func (s *Noop) Store(m *message.Message) error {
128130
// Query performs a query and attempts to fetch last n messages where
129131
// n is specified by limit argument. From and until times can also be specified
130132
// for time-series retrieval.
131-
func (s *Noop) Query(ssid message.Ssid, from, until time.Time, limit int) (message.Frame, error) {
133+
func (s *Noop) Query(ssid message.Ssid, from, until time.Time, startFromID message.ID, limit int) (message.Frame, error) {
132134
return nil, nil
133135
}
134136

internal/provider/storage/storage_test.go

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func TestNoop_Store(t *testing.T) {
4848
func TestNoop_Query(t *testing.T) {
4949
s := new(Noop)
5050
zero := time.Unix(0, 0)
51-
r, err := s.Query(testMessage(1, 2, 3).Ssid(), zero, zero, 10)
51+
r, err := s.Query(testMessage(1, 2, 3).Ssid(), zero, zero, nil, 10)
5252
assert.NoError(t, err)
5353
for range r {
5454
t.Errorf("Should be empty")
@@ -81,7 +81,7 @@ func testOrder(t *testing.T, store Storage) {
8181

8282
// Issue a query
8383
zero := time.Unix(0, 0)
84-
f, err := store.Query([]uint32{0, 1, 2}, zero, zero, 5)
84+
f, err := store.Query([]uint32{0, 1, 2}, zero, zero, nil, 5)
8585
assert.NoError(t, err)
8686

8787
assert.Len(t, f, 5)
@@ -104,7 +104,7 @@ func testRetained(t *testing.T, store Storage) {
104104

105105
// Issue a query
106106
zero := time.Unix(0, 0)
107-
f, err := store.Query([]uint32{0, 1, 2}, zero, zero, 1)
107+
f, err := store.Query([]uint32{0, 1, 2}, zero, zero, nil, 1)
108108
assert.NoError(t, err)
109109

110110
assert.Len(t, f, 1)
@@ -127,7 +127,7 @@ func testRange(t *testing.T, store Storage) {
127127
}
128128

129129
// Issue a query
130-
f, err := store.Query([]uint32{0, 1, 2}, time.Unix(t0, 0), time.Unix(t1, 0), 5)
130+
f, err := store.Query([]uint32{0, 1, 2}, time.Unix(t0, 0), time.Unix(t1, 0), nil, 5)
131131
assert.NoError(t, err)
132132

133133
assert.Len(t, f, 5)
@@ -153,6 +153,37 @@ func Test_configUint32(t *testing.T) {
153153
assert.Equal(t, uint32(99999999), v)
154154
}
155155

156+
// Test the StartFromID option for pagination purposes.
157+
func testStartFromID(t *testing.T, store Storage) {
158+
var fourth message.ID
159+
for i := int64(0); i < 10; i++ {
160+
payload := make([]byte, 1)
161+
payload[0] = byte(i)
162+
msg := message.New(message.Ssid{0, 1, 2}, []byte("a/b/c/"), payload)
163+
msg.TTL = message.RetainedTTL
164+
msg.ID.SetTime(msg.ID.Time() + (i * 10000))
165+
assert.NoError(t, store.Store(msg))
166+
if i == 4 {
167+
fourth = msg.ID
168+
}
169+
}
170+
171+
// Issue a query, starting at the fourth message ID and going back 2.
172+
zero := time.Unix(0, 0)
173+
f, err := store.Query([]uint32{0, 1, 2}, zero, zero, fourth, 2)
174+
assert.NoError(t, err)
175+
176+
assert.Len(t, f, 2)
177+
assert.Equal(t, 2, int(f[0].Payload[0]))
178+
assert.Equal(t, 3, int(f[1].Payload[0]))
179+
180+
// Issue a query, starting at the first message ID and going back 2.
181+
f, err = store.Query([]uint32{0, 1, 2}, zero, zero, f[0].ID, 2)
182+
assert.NoError(t, err)
183+
assert.Equal(t, 0, int(f[0].Payload[0]))
184+
assert.Equal(t, 1, int(f[1].Payload[0]))
185+
}
186+
156187
func testMaxResponseSizeReached(t *testing.T, store Storage) {
157188
for i := int64(0); i < 10; i++ {
158189
payload := make([]byte, mqtt.MaxMessageSize/5)
@@ -163,7 +194,7 @@ func testMaxResponseSizeReached(t *testing.T, store Storage) {
163194
}
164195

165196
zero := time.Unix(0, 0)
166-
f, err := store.Query([]uint32{0, 1, 2}, zero, zero, 10)
197+
f, err := store.Query([]uint32{0, 1, 2}, zero, zero, nil, 10)
167198
assert.NoError(t, err)
168199

169200
assert.Len(t, f, 4)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**********************************************************************************
2+
* Copyright (c) 2009-2020 Misakai Ltd.
3+
* This program is free software: you can redistribute it and/or modify it under the
4+
* terms of the GNU Affero General Public License as published by the Free Software
5+
* Foundation, either version 3 of the License, or(at your option) any later version.
6+
*
7+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
8+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
9+
* PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
10+
*
11+
* You should have received a copy of the GNU Affero General Public License along
12+
* with this program. If not, see<http://www.gnu.org/licenses/>.
13+
************************************************************************************/
14+
15+
package history
16+
17+
import (
18+
"encoding/json"
19+
20+
"github.com/emitter-io/emitter/internal/errors"
21+
"github.com/emitter-io/emitter/internal/message"
22+
"github.com/emitter-io/emitter/internal/provider/logging"
23+
"github.com/emitter-io/emitter/internal/security"
24+
"github.com/emitter-io/emitter/internal/service"
25+
)
26+
27+
// Request represents a historical messages request.
28+
type Request struct {
29+
Key string `json:"key"` // The channel key for this request.
30+
Channel string `json:"channel"` // The target channel for this request.
31+
StartFromID message.ID `json:"startFromID,omitempty"`
32+
}
33+
34+
type Message struct {
35+
ID message.ID `json:"id"`
36+
Topic string `json:"topic"` // The channel of the message
37+
Payload string `json:"payload"` // The payload of the message
38+
}
39+
type Response struct {
40+
Request uint16 `json:"req,omitempty"` // The corresponding request ID.
41+
Messages []Message `json:"messages"` // The history of messages.
42+
}
43+
44+
// ForRequest sets the request ID in the response for matching
45+
func (r *Response) ForRequest(id uint16) {
46+
r.Request = id
47+
}
48+
49+
// OnRequest handles a request of historical messages.
50+
func (s *Service) OnRequest(c service.Conn, payload []byte) (service.Response, bool) {
51+
var request Request
52+
if err := json.Unmarshal(payload, &request); err != nil {
53+
return errors.ErrBadRequest, false
54+
}
55+
56+
channel := security.ParseChannel([]byte(request.Channel))
57+
if channel.ChannelType == security.ChannelInvalid {
58+
return errors.ErrBadRequest, false
59+
}
60+
61+
// Check the authorization and permissions
62+
_, key, allowed := s.auth.Authorize(channel, security.AllowLoad)
63+
if !allowed {
64+
return errors.ErrUnauthorized, false
65+
}
66+
67+
// Use limit = 1 if not specified, otherwise use the limit option. The limit now
68+
// defaults to one as per MQTT spec we always need to send retained messages.
69+
limit := int64(1)
70+
if v, ok := channel.Last(); ok {
71+
limit = v
72+
}
73+
74+
ssid := message.NewSsid(key.Contract(), channel.Query)
75+
t0, t1 := channel.Window() // Get the window
76+
77+
msgs, err := s.store.Query(ssid, t0, t1, request.StartFromID, int(limit))
78+
if err != nil {
79+
logging.LogError("conn", "query last messages", err)
80+
return errors.ErrServerError, false
81+
}
82+
83+
resp := &Response{
84+
Messages: make([]Message, 0, len(msgs)),
85+
}
86+
for _, m := range msgs {
87+
msg := m
88+
resp.Messages = append(resp.Messages, Message{
89+
ID: msg.ID,
90+
Topic: string(msg.Channel), // The channel for this message.
91+
Payload: string(msg.Payload), // The payload for this message.
92+
})
93+
}
94+
return resp, true
95+
}

0 commit comments

Comments
 (0)