forked from scionproto/scion
-
Notifications
You must be signed in to change notification settings - Fork 0
/
setup_test.go
381 lines (361 loc) · 14.7 KB
/
setup_test.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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
// Copyright 2019 Anapaya Systems
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"sync"
"testing"
. "github.com/smartystreets/goconvey/convey"
"github.com/scionproto/scion/go/border/brconf"
"github.com/scionproto/scion/go/border/rctx"
"github.com/scionproto/scion/go/border/rpkt"
"github.com/scionproto/scion/go/lib/common"
"github.com/scionproto/scion/go/lib/log"
"github.com/scionproto/scion/go/lib/overlay"
"github.com/scionproto/scion/go/lib/ringbuf"
"github.com/scionproto/scion/go/lib/topology"
"github.com/scionproto/scion/go/lib/xtest"
)
var testInitOnce sync.Once
func TestSetupNet(t *testing.T) {
Convey("Setting up the same config should be a noop", t, func() {
r, oldCtx := setupTestRouter(t)
ctx := rctx.New(loadConfig(t))
clean := updateTestRouter(r, ctx, oldCtx)
defer clean()
// Check that the sockets are reused if nothing changes.
checkLocSocksUnchanged("New vs Old", ctx, oldCtx)
checkExtSocksUnchanged("New vs Old", ctx, oldCtx)
// Check that all sockets are still running
checkLocSocksRunning("ctx", ctx, true)
checkExtSocksRunning("ctx", ctx, true)
})
Convey("Setting up a config with changed local address should keep extSocks", t, func() {
r, oldCtx := setupTestRouter(t)
ctx := rctx.New(loadConfig(t))
// Modify local socket address. A new socket should be opened when
// setting up the context.
addr := ctx.Conf.BR.InternalAddrs
addr.PublicOverlay(addr.Overlay).L3().IP()[3] = 255
SoMsg("In", oldCtx.LocSockIn, ShouldNotBeNil)
clean := updateTestRouter(r, ctx, oldCtx)
defer clean()
// Check that the local socket changed
SoMsg("LocSockIn changed", ctx.LocSockIn, ShouldNotEqual, oldCtx.LocSockIn)
SoMsg("LocSockOut changed", ctx.LocSockOut, ShouldNotEqual, oldCtx.LocSockOut)
// Check that the external sockets are unchanged.
checkExtSocksUnchanged("New vs Old", ctx, oldCtx)
// Check that external sockets are still running.
checkExtSocksRunning("ctx", ctx, true)
})
Convey("Changing interface local address closes old socket", t, func() {
r, oldCtx := setupTestRouter(t)
copyCtx := copyContext(oldCtx)
ctx := rctx.New(loadConfig(t))
ctx.Conf.BR.IFs[12].Local.PublicOverlay(overlay.IPv4).L3().IP()[3] = 255
clean := updateTestRouter(r, ctx, oldCtx)
defer clean()
// Check that unaffected sockets have not changed.
checkLocSocksUnchanged("New vs copy", ctx, copyCtx)
checkLocSocksUnchanged("Copy vs old", copyCtx, oldCtx)
checkExtSocksUnchanged("Copy vs old", copyCtx, oldCtx)
// Keep socket for unchanged interface.
SoMsg("IFID 11", ctx.ExtSockIn[11], ShouldEqual, oldCtx.ExtSockIn[11])
SoMsg("IFID 11", ctx.ExtSockOut[11], ShouldEqual, oldCtx.ExtSockOut[11])
// Change socket for modified interface.
SoMsg("IFID 12", ctx.ExtSockIn[12], ShouldNotEqual, oldCtx.ExtSockIn[12])
SoMsg("IFID 12", ctx.ExtSockOut[12], ShouldNotEqual, oldCtx.ExtSockOut[12])
// Old socket must be closed.
SoMsg("Old 12 In running", oldCtx.ExtSockIn[12].Running(), ShouldBeFalse)
SoMsg("Old 12 Out running", oldCtx.ExtSockOut[12].Running(), ShouldBeFalse)
})
Convey("Changing interface remote address closes old socket", t, func() {
r, oldCtx := setupTestRouter(t)
copyCtx := copyContext(oldCtx)
ctx := rctx.New(loadConfig(t))
ctx.Conf.BR.IFs[12].Remote.L3().IP()[3] = 255
clean := updateTestRouter(r, ctx, oldCtx)
defer clean()
// Check that unaffected sockets have not changed.
checkLocSocksUnchanged("New vs copy", ctx, copyCtx)
checkLocSocksUnchanged("Copy vs old", copyCtx, oldCtx)
checkExtSocksUnchanged("Copy vs old", copyCtx, oldCtx)
// Keep socket for unchanged interface.
SoMsg("IFID 11", ctx.ExtSockIn[11], ShouldEqual, oldCtx.ExtSockIn[11])
SoMsg("IFID 11", ctx.ExtSockOut[11], ShouldEqual, oldCtx.ExtSockOut[11])
// Change socket for modified interface.
SoMsg("IFID 12", ctx.ExtSockIn[12], ShouldNotEqual, oldCtx.ExtSockIn[12])
SoMsg("IFID 12", ctx.ExtSockOut[12], ShouldNotEqual, oldCtx.ExtSockOut[12])
// Old socket must be closed.
SoMsg("Old 12 In running", oldCtx.ExtSockIn[12].Running(), ShouldBeFalse)
SoMsg("Old 12 Out running", oldCtx.ExtSockOut[12].Running(), ShouldBeFalse)
})
}
func TestRollbackNet(t *testing.T) {
Convey("Rolling back the same config should be a noop", t, func() {
r, oldCtx := setupTestRouter(t)
copyCtx := copyContext(oldCtx)
ctx := rctx.New(loadConfig(t))
clean := updateTestRouter(r, ctx, oldCtx)
defer clean()
// Rollback the changes.
r.rollbackNet(ctx, oldCtx, brconf.SockConf{Default: PosixSock}, func(err error) {
SoMsg("Rollback err", err, ShouldBeNil)
})
// Check that the original context has not been modified.
checkLocSocksUnchanged("Old vs copy", oldCtx, copyCtx)
checkExtSocksUnchanged("Old vs copy", oldCtx, copyCtx)
// Check that all sockets are still running
checkLocSocksRunning("Old", oldCtx, true)
checkExtSocksRunning("Old", oldCtx, true)
})
Convey("Rolling back config with changed local address does "+
"not affect external sockets", t, func() {
r, oldCtx := setupTestRouter(t)
copyCtx := copyContext(oldCtx)
ctx := rctx.New(loadConfig(t))
addr := ctx.Conf.BR.InternalAddrs
addr.PublicOverlay(addr.Overlay).L3().IP()[3] = 255
clean := updateTestRouter(r, ctx, oldCtx)
defer clean()
// Rollback the changes.
r.rollbackNet(ctx, oldCtx, brconf.SockConf{Default: PosixSock}, func(err error) {
SoMsg("Rollback err", err, ShouldBeNil)
})
// Check that the external interfaces of original context has not been modified.
checkExtSocksUnchanged("Old vs copy", oldCtx, copyCtx)
// Check that all sockets are still running
checkLocSocksRunning("Old", oldCtx, true)
checkExtSocksRunning("Old", oldCtx, true)
})
Convey("Rolling back config with changed external interface "+
"does not affect local socket", t, func() {
r, oldCtx := setupTestRouter(t)
copyCtx := copyContext(oldCtx)
ctx := rctx.New(loadConfig(t))
ctx.Conf.BR.IFs[12].Local.PublicOverlay(overlay.IPv4).L3().IP()[3] = 255
clean := updateTestRouter(r, ctx, oldCtx)
defer clean()
// Rollback the changes.
r.rollbackNet(ctx, oldCtx, brconf.SockConf{Default: PosixSock}, func(err error) {
SoMsg("Rollback err", err, ShouldBeNil)
})
// Check that the local socket of the original context has not been modified.
checkLocSocksUnchanged("Old vs copy", oldCtx, copyCtx)
// Check that all sockets are still running
checkLocSocksRunning("Old", oldCtx, true)
checkExtSocksRunning("Old", oldCtx, true)
// Check the freshly created sockets are stopped.
SoMsg("New Ifid 12 In running", ctx.ExtSockIn[12].Running(), ShouldBeFalse)
SoMsg("New ifid 12 Out running", ctx.ExtSockOut[12].Running(), ShouldBeFalse)
})
}
func TestTeardownNet(t *testing.T) {
Convey("Tearing down the same config should be a noop", t, func() {
r, oldCtx := setupTestRouter(t)
ctx := rctx.New(loadConfig(t))
clean := updateTestRouter(r, ctx, oldCtx)
defer clean()
// Start sockets on the new context.
startSocks(ctx)
// Create copy of the new context to catch changes.
copyCtx := copyContext(ctx)
r.teardownNet(ctx, oldCtx, brconf.SockConf{Default: PosixSock})
// Check that teardown does not modify the context
checkLocSocksUnchanged("New vs copy", ctx, copyCtx)
checkExtSocksUnchanged("New vs copy", ctx, copyCtx)
// Check that teardown does not close the needed sockets.
checkExtSocksRunning("New", ctx, true)
checkLocSocksRunning("New", ctx, true)
})
Convey("Tearing down config with changed local address should be a noop", t, func() {
r, oldCtx := setupTestRouter(t)
ctx := rctx.New(loadConfig(t))
addr := ctx.Conf.BR.InternalAddrs
addr.PublicOverlay(addr.Overlay).L3().IP()[3] = 255
clean := updateTestRouter(r, ctx, oldCtx)
defer clean()
// Start sockets on the new context.
startSocks(ctx)
// Create copy of the new context to catch changes.
copyCtx := copyContext(ctx)
r.teardownNet(ctx, oldCtx, brconf.SockConf{Default: PosixSock})
// Check that teardown does not modify the context
checkLocSocksUnchanged("New vs copy", ctx, copyCtx)
checkExtSocksUnchanged("New vs copy", ctx, copyCtx)
// Check that teardown does not close the needed sockets.
checkExtSocksRunning("New", ctx, true)
checkLocSocksRunning("New", ctx, true)
})
Convey("Tearing down config with changed interface should be a noop", t, func() {
r, oldCtx := setupTestRouter(t)
ctx := rctx.New(loadConfig(t))
ctx.Conf.BR.IFs[12].Local.PublicOverlay(overlay.IPv4).L3().IP()[3] = 255
clean := updateTestRouter(r, ctx, oldCtx)
defer clean()
// Start sockets on the new context.
startSocks(ctx)
// Create copy of the new context to catch changes.
// Create copy of the new context to catch changes.
copyCtx := copyContext(ctx)
r.teardownNet(ctx, oldCtx, brconf.SockConf{Default: PosixSock})
// Check that teardown does not modify the context
checkLocSocksUnchanged("New vs copy", ctx, copyCtx)
checkExtSocksUnchanged("New vs copy", ctx, copyCtx)
// Check that teardown does not close the needed sockets.
checkExtSocksRunning("New", ctx, true)
checkLocSocksRunning("New", ctx, true)
})
Convey("Tearing down config with removed interface should close socket", t, func() {
r, oldCtx := setupTestRouter(t)
ctx := rctx.New(loadConfig(t))
delete(ctx.Conf.BR.IFs, 12)
clean := updateTestRouter(r, ctx, oldCtx)
defer clean()
// Start sockets on the new context.
startSocks(ctx)
// Create copy of the new context to catch changes.
// Create copy of the new context to catch changes.
copyCtx := copyContext(ctx)
r.teardownNet(ctx, oldCtx, brconf.SockConf{Default: PosixSock})
// Check that teardown does not modify the context
checkLocSocksUnchanged("New vs copy", ctx, copyCtx)
checkExtSocksUnchanged("New vs copy", ctx, copyCtx)
// Check that teardown does not close the needed sockets.
checkExtSocksRunning("New", ctx, true)
checkLocSocksRunning("New", ctx, true)
// Check removed interface is no longer running
SoMsg("New Ifid 12 In running", oldCtx.ExtSockIn[12].Running(), ShouldBeFalse)
SoMsg("New ifid 12 Out running", oldCtx.ExtSockOut[12].Running(), ShouldBeFalse)
})
}
// checkLocSocksUnchanged compares that both contexts point to the same local socket.
func checkLocSocksUnchanged(key string, ctx, oldCtx *rctx.Ctx) {
SoMsg(fmt.Sprintf("%s: LocSockIn unchanged", key), ctx.LocSockIn, ShouldEqual, oldCtx.LocSockIn)
SoMsg(fmt.Sprintf("%s: LocSockOut unchanged", key),
ctx.LocSockOut, ShouldEqual, oldCtx.LocSockOut)
}
// checkExtSocksUnchaged compares that both contexts point to the same external sockets.
func checkExtSocksUnchanged(key string, ctx, oldCtx *rctx.Ctx) {
// Check that all sockets that are in oldCtx are in ctx.
compareExtSocksEq(key, oldCtx.ExtSockIn, ctx.ExtSockIn, "oldCtxIn vs ctxIn")
compareExtSocksEq(key, oldCtx.ExtSockOut, ctx.ExtSockOut, "oldCtxOut vs ctxOut")
// Check that all sockets that are in ctx are in oldCtx
compareExtSocksEq(key, ctx.ExtSockIn, oldCtx.ExtSockIn, "ctxIn vs oldCtxIn")
compareExtSocksEq(key, ctx.ExtSockOut, oldCtx.ExtSockOut, "ctxOut vs oldCtxOut")
}
func compareExtSocksEq(key string, a, b map[common.IFIDType]*rctx.Sock, suffix string) {
for ifid, sock := range a {
SoMsg(fmt.Sprintf("%s: IFID %d %s", key, ifid, suffix), sock, ShouldEqual, b[ifid])
}
}
func checkLocSocksRunning(key string, ctx *rctx.Ctx, running bool) {
SoMsg(fmt.Sprintf("%s: LocSockIn running", key), ctx.LocSockIn.Running(), ShouldEqual, running)
SoMsg(fmt.Sprintf("%s: LocSockOut running", key),
ctx.LocSockOut.Running(), ShouldEqual, running)
}
func checkExtSocksRunning(key string, ctx *rctx.Ctx, running bool) {
for ifid, sock := range ctx.ExtSockIn {
SoMsg(fmt.Sprintf("%s: IFID %d In", key, ifid), sock.Running(), ShouldEqual, running)
}
for ifid, sock := range ctx.ExtSockOut {
SoMsg(fmt.Sprintf("%s: IFID %d Out", key, ifid), sock.Running(), ShouldEqual, running)
}
}
// setupTest sets up a test router. The test router is initially set up with the
// topology loaded from testdata.
func setupTestRouter(t *testing.T) (*Router, *rctx.Ctx) {
r := initTestRouter(4)
sockConf := brconf.SockConf{Default: PosixSock}
// oldCtx contains the testdata topology.
oldCtx := rctx.New(loadConfig(t))
xtest.FailOnErr(t, r.setupNet(oldCtx, nil, sockConf))
startSocks(oldCtx)
return r, oldCtx
}
func initTestRouter(maxNumPosixInput int) *Router {
// Init metrics.
testInitOnce.Do(func() {
// Reduce output displayed in goconvey.
log.Root().SetHandler(log.DiscardHandler())
})
// The number of free packets has to be at least the number of posix
// input routines times inputBufCnt. Otherwise they might get stuck
// trying to prepare for reading from the connection.
// See: https://github.com/scionproto/scion/issues/1981
r := &Router{
freePkts: ringbuf.New(maxNumPosixInput*inputBufCnt, func() interface{} {
return rpkt.NewRtrPkt()
}, "free_pkts"),
}
return r
}
// updateTestRouter calls setupNet on the provided router with new and old context.
// The cleanup function shall be called to free the allocated sockets.
func updateTestRouter(r *Router, newCtx, oldCtx *rctx.Ctx) func() {
// Copy the context to make sure all sockets are closed,
// even if socket pointers are modified in oldCtx.
copyCtx := copyContext(oldCtx)
err := r.setupNet(newCtx, oldCtx, brconf.SockConf{Default: PosixSock})
SoMsg("err", err, ShouldBeNil)
// Close all sockets to allow binding in subsequent tests.
cleanUp := func() {
closeAllSocks(newCtx)
closeAllSocks(oldCtx)
closeAllSocks(copyCtx)
}
return cleanUp
}
func copyContext(ctx *rctx.Ctx) *rctx.Ctx {
c := &rctx.Ctx{}
*c = *ctx
c.ExtSockIn = make(map[common.IFIDType]*rctx.Sock)
c.ExtSockOut = make(map[common.IFIDType]*rctx.Sock)
for ifid, sock := range ctx.ExtSockIn {
c.ExtSockIn[ifid] = sock
}
for ifid, sock := range ctx.ExtSockOut {
c.ExtSockOut[ifid] = sock
}
return c
}
func closeAllSocks(ctx *rctx.Ctx) {
if ctx != nil {
ctx.LocSockIn.Stop()
ctx.LocSockOut.Stop()
for ifid := range ctx.ExtSockIn {
ctx.ExtSockIn[ifid].Stop()
ctx.ExtSockOut[ifid].Stop()
}
}
}
func loadConfig(t *testing.T) *brconf.BRConf {
topo := loadTopo(t)
topo, err := topology.LoadFromFile("testdata/topology.json")
xtest.FailOnErr(t, err)
topoBr, ok := topo.BR["br1-ff00_0_111-1"]
if !ok {
t.Fatal("BR ID not found")
}
return &brconf.BRConf{
Topo: topo,
IA: topo.ISD_AS,
BR: &topoBr,
}
}
func loadTopo(t *testing.T) *topology.Topo {
topo, err := topology.LoadFromFile("testdata/topology.json")
xtest.FailOnErr(t, err)
return topo
}