forked from thejerf/reign
/
node.go
295 lines (252 loc) · 8.53 KB
/
node.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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
package reign
// In order to make this work:
// * There must be an "address" type that can serialize out as an ID
// and nothing but an ID, then deserialize as either a local or remote
// inside the concrete type. Then the GobEncoder and GobDecoder types can
// correctly manage the mailboxes.
// * send messages across
// * route the sent messages to the target mailboxes
// * implement the policy below
// * Implement the remote backpressure.
// FIXME: To be added to the documentation
// Unlike what you may initially suspect, when sending a message to a remote
// node, you will block until that message has been queued up and is ready
// to send. (That is, you will not block while the message is being *sent*,
// so if you have a large message that may take a moment to send you can
// move on.) This is for the purpose of providing backpressure; if the
// connection to the remote node becomes overloaded, it is desirable for
// things using that connection to be slowed up a bit. This may not be
// the optimal policy, but it's a start. In the case where messages are
// being sent substantially slower than the network can handle, the
// desirable common case, this will amount to no pauses.
//
// we can get this pattern by having the senders send to the node, and
// having them also select on the node's status chan; if that gets closed
// due to failure, we give up. We can also select on a timeout chan.
//
// FIXME: This implies that the backpressure of a node ought to be propagated
// back to the cluster itself. "Sufficiently large" backlogs should be propagated
// to any senders, so they can be slowed down locally.
import (
"crypto/tls"
"encoding/gob"
"errors"
"fmt"
"io"
"net"
"sync"
"github.com/thejerf/reign/internal"
)
const (
clusterVersion = 1
)
// nodeConnector bundles together all of the information about how to connect
// to a node. The actual connection is a nodeConnection. This runs as a
// supervised servec.
type nodeConnector struct {
source *NodeDefinition
dest *NodeDefinition
cluster *Cluster
cancel bool
ClusterLogger
// this mailbox should never get out to the "rest" of the system.
remoteMailboxes *remoteMailboxes
sync.Mutex
// deliberately retained across restarts
*mailboxes
connectionServer *connectionServer
connection *nodeConnection
failOnSSLHandshake bool
failOnClusterHandshake bool
}
// this is the literal connection to the node.
type nodeConnection struct {
conn net.Conn
tls *tls.Conn
rawOutput io.WriteCloser
rawInput io.ReadCloser
output *gob.Encoder
input *gob.Decoder
connectionServer *connectionServer
source *NodeDefinition
dest *NodeDefinition
ClusterLogger
*nodeConnector
failOnSSLHandshake bool
failOnClusterHandshake bool
}
func (nc *nodeConnector) String() string {
return fmt.Sprintf("nodeConnector %d -> %d", nc.source.ID, nc.dest.ID)
}
func (nc *nodeConnector) Serve() {
nc.Trace("node connection from %d to %d starting serve", nc.source.ID, nc.dest.ID)
connection, err := nc.connect()
nc.connection = connection
if err != nil {
nc.Error("Could not connect to node %v: %s", nc.dest.ID, err.Error())
return
}
nc.Trace("%d -> %d connected", nc.source.ID, nc.dest.ID)
// sync with the Stop method, which could conceivably be triggered
// before we even get here
nc.Lock()
if nc.cancel {
connection.terminate()
}
defer connection.terminate()
nc.Unlock()
err = connection.sslHandshake()
if err != nil {
nc.Error("Could not SSL handshake to node %v: %s", nc.dest.ID, err.Error())
return
}
nc.Trace("%d -> %d ssl handshake successful", nc.source.ID, nc.dest.ID)
err = connection.clusterHandshake()
if err != nil {
nc.Error("Could not perform cluster handshake with node %v: %s", nc.dest.ID, err.Error())
return
}
nc.Trace("%d -> %d cluster handshake successful", nc.source.ID, nc.dest.ID)
// hook up the connection to the permanent message manager
nc.remoteMailboxes.setConnection(connection)
defer nc.remoteMailboxes.unsetConnection(connection)
// and handle all incoming messages
connection.handleIncomingMessages()
}
func (nc *nodeConnector) Stop() {
nc.Lock()
defer nc.Unlock()
if nc.connection != nil {
nc.connection.terminate()
nc.connection = nil
} else {
nc.cancel = true
}
}
func (nc *nodeConnection) terminate() {
if nc == nil {
return
}
tls := nc.tls
if tls != nil {
tls.Close()
}
conn := nc.conn
if conn != nil {
conn.Close()
}
rawOutput := nc.rawOutput
if rawOutput != nil {
rawOutput.Close()
}
rawInput := nc.rawInput
if rawInput != nil {
rawInput.Close()
}
}
// This establishes a connection to the target node. It does NOTHING ELSE,
// no SSL handshake, nothing.
//
// FIXME: Test that a node definition can't establish two connections to
// the same node.
func (nc *nodeConnector) connect() (*nodeConnection, error) {
conn, err := net.DialTCP("tcp", nc.source.localaddr, nc.dest.ipaddr)
if err != nil {
return nil, err
}
return &nodeConnection{
conn: conn,
rawOutput: conn,
rawInput: conn,
source: nc.source,
dest: nc.dest,
ClusterLogger: nc.ClusterLogger,
connectionServer: nc.connectionServer,
failOnSSLHandshake: nc.failOnSSLHandshake,
failOnClusterHandshake: nc.failOnClusterHandshake,
nodeConnector: nc,
}, nil
}
func (nc *nodeConnection) sslHandshake() error {
nc.Trace("Conn to %d in sslHandshake", nc.dest.ID)
if nc.failOnSSLHandshake {
nc.conn.Close()
nc.Trace("Failing on ssl handshake, as instructed")
return errors.New("failing on ssl handshake, as instructed")
}
tlsConfig := nc.connectionServer.Cluster.tlsConfig(nc.dest.ID)
tlsConn := tls.Client(nc.conn, tlsConfig)
// I like to run this manually, despite the fact this is run
// automatically at first communication,, so I get any errors it may
// produce at a controlled time.
nc.Trace("Conn to %d handshaking", nc.dest.ID)
err := tlsConn.Handshake()
nc.Trace("Conn to %d handshook, err: %#v", nc.dest.ID, err)
if err != nil {
return err
}
nc.tls = tlsConn
// Initially, we unconditionally use the TLS connection
nc.output = gob.NewEncoder(nc.tls)
nc.input = gob.NewDecoder(nc.tls)
return nil
}
func (nc *nodeConnection) clusterHandshake() error {
if nc.failOnClusterHandshake {
nc.tls.Close()
nc.Trace("Failing on cluster handshake, as instructed")
return errors.New("failing on cluster handshake, as instructed")
}
handshake := internal.ClusterHandshake{
ClusterVersion: clusterVersion,
MyNodeID: internal.IntNodeID(nc.source.ID),
YourNodeID: internal.IntNodeID(nc.dest.ID),
}
nc.output.Encode(handshake)
var serverHandshake internal.ClusterHandshake
err := nc.input.Decode(&serverHandshake)
if err != nil {
return err
}
myNodeID := NodeID(serverHandshake.MyNodeID)
yourNodeID := NodeID(serverHandshake.YourNodeID)
if serverHandshake.ClusterVersion != clusterVersion {
connections.Warn("Remote node id %v claimed unknown cluster version %v, proceeding in the hope that this will all just work out somehow...", nc.dest.ID, serverHandshake.ClusterVersion)
}
if myNodeID != nc.dest.ID {
connections.Warn("The node I thought was #%v is claiming to be #%v instead. These two nodes can not communicate properly. Standing by, hoping a new node definition will resolve this shortly....", nc.dest.ID, serverHandshake.MyNodeID)
}
if yourNodeID != nc.source.ID {
connections.Warn("The node #%v thinks I'm node #%v, but I think I'm node #%v. These two nodes can not communicate properly. Standing by, hoping a new node definition will resolve this shortly...", nc.dest.ID, serverHandshake.YourNodeID, nc.source.ID)
}
nc.input = gob.NewDecoder(nc.tls)
return nil
}
func (nc *nodeConnection) handleIncomingMessages() {
var cm internal.ClusterMessage
var err error
nc.Trace("Connection %d -> %d in handleIncomingMessages", nc.source.ID, nc.dest.ID)
for err == nil {
// FIXME: Set read timeouts, to handle network errors
// FIXME: Send pings, to avoid triggering read timeouts under normal
// circumstances
err = nc.input.Decode(&cm)
if err == nil {
err = nc.nodeConnector.remoteMailboxes.Send(cm)
if err != nil {
nc.Error("Error handling message %#v:\n%#v", cm, err)
}
} else {
panic(fmt.Sprintf("Error decoding message: %s", err.Error()))
}
}
}
func (nc *nodeConnection) send(value *internal.ClusterMessage) error {
// If we are not currently connected, silently eat the message.
// FIXME: Compare with Erlang.
if nc == nil {
return errors.New("no current connection")
}
// FIXME: Send timeout
return nc.output.Encode(value)
}