This repository has been archived by the owner on Nov 30, 2019. It is now read-only.
/
peter.go
263 lines (235 loc) · 7.74 KB
/
peter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
package peter
import (
"encoding/hex"
"log"
"os"
"secondbit.org/wendy"
)
const (
MSG_SUBSCRIBE = byte(17)
MSG_UNSUBSCRIBE = byte(18)
MSG_EVENT = byte(19)
)
const (
LogLevelDebug = iota
LogLevelWarn
LogLevelError
)
type Topic string
type topicSlice []Topic
func (t topicSlice) Len() int { return len(t) }
func (t topicSlice) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t topicSlice) Less(i, j int) bool { return t[i] < t[j] }
type Peter struct {
subscriptions *subscriptionMap
parents *parentMap
cluster *wendy.Cluster
logLevel int
log *log.Logger
}
// New creates a new instance of Peter, complete with a Node and the underlying Wendy Cluster, and registers itself to receive callbacks from the Cluster events.
func New(id wendy.NodeID, localIP, globalIP, region string, port int) *Peter {
node := wendy.NewNode(id, localIP, globalIP, region, port)
cluster := wendy.NewCluster(node, nil)
peter := &Peter{
subscriptions: newSubscriptionMap(),
parents: newParentMap(),
cluster: cluster,
log: log.New(os.Stdout, "peter("+id.String()+") ", log.LstdFlags),
logLevel: LogLevelWarn,
}
cluster.RegisterCallback(peter)
return peter
}
// Listen starts the current Node listening for messages. A Node must then use the Join method to join a Cluster.
func (p *Peter) Listen() error {
return p.cluster.Listen()
}
// Stop causes the current Node to exit the Cluster after it attempts to notify known Nodes of its departure, resulting in a graceful departure.
func (p *Peter) Stop() {
p.cluster.Stop()
}
// Kill causes the current Node to immediately exit the Cluster, with no attempt at a graceful departure.
func (p *Peter) Kill() {
p.cluster.Kill()
}
// Join includes the current Node in the Cluster that the Node specified by the provided IP and port is part of.
func (p *Peter) Join(ip string, port int) error {
return p.cluster.Join(ip, port)
}
// Subscribe broadcasts the current Node's interest in a Topic, allowing it to receive future event notifications from that Topic.
func (p *Peter) Subscribe(t Topic) error {
key, err := wendy.NodeIDFromBytes([]byte(string(t)))
if err != nil {
return err
}
msg := p.cluster.NewMessage(MSG_SUBSCRIBE, key, []byte{})
err = p.cluster.Send(msg)
return err
}
// Unsubscribe broadcasts that the current Node is no longer interested in a Topic, preventing it from receiving future event notifications for that Topic.
func (p *Peter) Unsubscribe(t Topic) error {
key, err := wendy.NodeIDFromBytes([]byte(string(t)))
if err != nil {
return err
}
msg := p.cluster.NewMessage(MSG_UNSUBSCRIBE, key, []byte{})
err = p.cluster.Send(msg)
return err
}
// Broadcast sends an event notification for a Topic that will be sent to every Node subscribed to that Topic.
func (p *Peter) Broadcast(t Topic, body []byte) error {
key, err := wendy.NodeIDFromBytes([]byte(string(t)))
if err != nil {
return err
}
msg := p.cluster.NewMessage(MSG_EVENT, key, body)
err = p.cluster.Send(msg)
return err
}
// OnError fulfills the wendy.Application interface. It will be called when the cluster encounters an error that it is unable to handle.
func (p *Peter) OnError(err error) {
}
// OnDeliver fulfills the wendy.Application interface. It will be called when a message is at the end of its routing path. Peter uses it to build the subscription tree.
func (p *Peter) OnDeliver(msg wendy.Message) {
val := Topic(string(msg.Value))
switch msg.Purpose {
case MSG_SUBSCRIBE:
p.subscriptions.insert(val, msg.Sender.ID)
break
case MSG_UNSUBSCRIBE:
p.subscriptions.remove(val, msg.Sender.ID)
break
case MSG_EVENT:
p.notifySubscribers(val)
break
}
}
// OnForward fulfills the wendy.Application interface. It will be called when a message is about to be passed on to the next Node in its routing path. Peter uses it to build the subscription tree, and may prematurely end the message's routing path.
func (p *Peter) OnForward(msg *wendy.Message, nextId wendy.NodeID) bool {
val := Topic(string(msg.Value))
switch msg.Purpose {
case MSG_SUBSCRIBE:
inserted := p.subscriptions.insert(val, msg.Sender.ID)
if inserted {
t, err := hex.DecodeString(msg.Key.String())
if err != nil {
p.err(err.Error())
return false
}
err = p.Subscribe(Topic(t))
if err != nil {
p.err(err.Error())
return false
}
// Prevent the message from continuing
return false
}
break
case MSG_UNSUBSCRIBE:
removed, empty := p.subscriptions.remove(val, msg.Sender.ID)
if removed {
if empty {
t, err := hex.DecodeString(msg.Key.String())
if err != nil {
p.err(err.Error())
return false
}
err = p.Unsubscribe(Topic(t))
if err != nil {
p.err(err.Error())
return false
}
}
// Prevent the message from continuing
return false
}
break
case MSG_EVENT:
p.notifySubscribers(val)
break
}
return true
}
// OnNewLeaves fulfills the wendy.Application interface. It will be called when Wendy's leaf set is updated. It is a stub.
func (p *Peter) OnNewLeaves(leafset []*wendy.Node) {
}
// OnNodeJoin fulfills the wendy.Application interface, and will be called whenever a Node leaves the Cluster. Peter will detect which topics would use that Node as a parent in the subscription tree, and re-subscribe to those topics to repair the subscription tree.
func (p *Peter) OnNodeJoin(node wendy.Node) {
topics := p.parents.topics()
for _, topic := range topics {
key, err := wendy.NodeIDFromBytes([]byte(string(topic)))
if err != nil {
p.err(err.Error())
}
target, err := p.cluster.Route(key)
if err != nil {
p.err(err.Error())
}
if node.ID.Equals(target.ID) {
err = p.Unsubscribe(topic)
if err != nil {
p.err(err.Error())
}
err = p.Subscribe(topic)
if err != nil {
p.err(err.Error())
}
}
}
}
// OnNodeExit fulfills the wendy.Application interface, and will be called whenever a Node leaves the Cluster. Peter will re-subscribe to any topics that Node was responsible for updating the current Node about, repairing the subscription tree.
func (p *Peter) OnNodeExit(node wendy.Node) {
topics := p.parents.topicsByID(node.ID)
for _, topic := range topics {
err := p.Subscribe(topic)
if err != nil {
p.err(err.Error())
}
}
p.subscriptions.removeSubscriber(node.ID)
}
// OnHeartbeat exists only to fulfill the wendy.Application interface. It will be called whenever a heartbeat is received in Wendy, checking for Node health. It is a stub.
func (p *Peter) OnHeartbeat(node wendy.Node) {
}
func (p *Peter) notifySubscribers(t Topic) error {
// TODO: fan out notification to each subscriber
return nil
}
// SetLogger sets the log.Logger that Peter and its underlying cluster will write to.
func (p *Peter) SetLogger(l *log.Logger) {
p.log = l
}
// SetLogLevel sets the level of logging that will be written to the Logger. It will be mirrored to the cluster.
//
// Use peter.LogLevelDebug to write to the most verbose level of logging, helpful for debugging.
//
// Use peter.LogLevelWarn (the default) to write on events that may, but do not necessarily, indicate an error.
//
// Use peter.LogLevelError to write only when an event occurs that is undoubtedly an error.
func (p *Peter) SetLogLevel(level int) {
p.logLevel = level
switch level {
case LogLevelDebug:
p.cluster.SetLogLevel(wendy.LogLevelDebug)
case LogLevelWarn:
p.cluster.SetLogLevel(wendy.LogLevelWarn)
case LogLevelError:
p.cluster.SetLogLevel(wendy.LogLevelError)
}
}
func (p *Peter) debug(format string, v ...interface{}) {
if p.logLevel <= LogLevelDebug {
p.log.Printf(format, v...)
}
}
func (p *Peter) warn(format string, v ...interface{}) {
if p.logLevel <= LogLevelWarn {
p.log.Printf(format, v...)
}
}
func (p *Peter) err(format string, v ...interface{}) {
if p.logLevel <= LogLevelError {
p.log.Printf(format, v...)
}
}