blob: fbbc802d883b7afa7a85d563ecfc75838f601581 [file] [log] [blame]
khenaidoo820197c2020-02-13 16:35:33 -05001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package route
17
18import (
19 "context"
20 "errors"
21 "fmt"
22 "github.com/opencord/voltha-protos/v3/go/openflow_13"
23 "github.com/opencord/voltha-protos/v3/go/voltha"
24 "github.com/stretchr/testify/assert"
25 "math/rand"
26 "reflect"
27 "strings"
28 "sync"
29 "testing"
30 "time"
31)
32
33const (
34 logicalDeviceID = "ld"
35 oltDeviceID = "olt"
36)
37
khenaidoo0db4c812020-05-27 15:27:30 -040038const testSetupPhase contextKey = "testSetupPhase"
39
40type contextKey string
41
khenaidoo820197c2020-02-13 16:35:33 -050042//portRegistration is a message sent from an OLT device to a logical device to create a logical port
43type portRegistration struct {
44 port *voltha.Port
45 rootPort bool
46}
47
48//onuRegistration is a message sent from an ONU device to an OLT device to register an ONU
49type onuRegistration struct {
50 onu *voltha.Device
51 oltPonNo uint32
52 onuPonNo uint32
53}
54
55type logicalDeviceManager struct {
56 logicalDevice *voltha.LogicalDevice
57 deviceRoutes *DeviceRoutes
58 ldChnl chan portRegistration
59 numLogicalPorts int
60 done chan struct{}
61}
62
63func newLogicalDeviceManager(ld *voltha.LogicalDevice, ch chan portRegistration, totalLogicalPorts int, done chan struct{}) *logicalDeviceManager {
64 return &logicalDeviceManager{
65 logicalDevice: ld,
66 ldChnl: ch,
67 numLogicalPorts: totalLogicalPorts,
68 done: done,
69 }
70}
71
72func (ldM *logicalDeviceManager) start(getDevice GetDeviceFunc, buildRoutes bool) {
73 ldM.deviceRoutes = NewDeviceRoutes(ldM.logicalDevice.Id, getDevice)
74 ofpPortNo := uint32(1)
75 for portReg := range ldM.ldChnl {
76 if portReg.port == nil {
77 // End of registration - exit loop
78 break
79 }
80 lp := &voltha.LogicalPort{
81 Id: portReg.port.Label,
82 OfpPort: &openflow_13.OfpPort{PortNo: ofpPortNo},
83 DeviceId: portReg.port.DeviceId,
84 DevicePortNo: portReg.port.PortNo,
85 RootPort: portReg.rootPort,
86 }
87 ldM.logicalDevice.Ports = append(ldM.logicalDevice.Ports, lp)
88 if buildRoutes {
khenaidoo0db4c812020-05-27 15:27:30 -040089 device, err := getDevice(context.WithValue(context.Background(), testSetupPhase, true), lp.DeviceId)
90 if err != nil {
91 fmt.Println("Error when getting device:", lp.DeviceId, err)
92 }
93 err = ldM.deviceRoutes.AddPort(context.Background(), lp, device, ldM.logicalDevice.Ports)
khenaidoo820197c2020-02-13 16:35:33 -050094 if err != nil && !strings.Contains(err.Error(), "code = FailedPrecondition") {
95 fmt.Println("(Error when adding port:", lp, len(ldM.logicalDevice.Ports), err)
96 }
97 }
98 ofpPortNo++
99 }
100 // Inform the caller we are now done
101 ldM.done <- struct{}{}
102}
103
104type oltManager struct {
105 olt *voltha.Device
106 logicalDeviceMgr *logicalDeviceManager
107 numNNIPort int
108 numPonPortOnOlt int
109 oltChnl chan onuRegistration
110}
111
112func newOltManager(oltDeviceID string, ldMgr *logicalDeviceManager, numNNIPort int, numPonPortOnOlt int, ch chan onuRegistration) *oltManager {
113 return &oltManager{
114 olt: &voltha.Device{Id: oltDeviceID, ParentId: ldMgr.logicalDevice.Id, Root: true},
115 logicalDeviceMgr: ldMgr,
116 numNNIPort: numNNIPort,
117 numPonPortOnOlt: numPonPortOnOlt,
118 oltChnl: ch,
119 }
120}
121
122func (oltM *oltManager) start() {
123 oltM.olt.Ports = make([]*voltha.Port, 0)
124 // Setup the OLT nni ports and trigger the nni ports creation
125 for nniPort := 1; nniPort < oltM.numNNIPort+1; nniPort++ {
126 p := &voltha.Port{Label: fmt.Sprintf("nni-%d", nniPort), PortNo: uint32(nniPort), DeviceId: oltM.olt.Id, Type: voltha.Port_ETHERNET_NNI}
127 oltM.olt.Ports = append(oltM.olt.Ports, p)
128 oltM.logicalDeviceMgr.ldChnl <- portRegistration{port: p, rootPort: true}
129 }
130
131 // Create OLT pon ports
132 for ponPort := oltM.numNNIPort + 1; ponPort < oltM.numPonPortOnOlt+oltM.numNNIPort+1; ponPort++ {
133 p := voltha.Port{PortNo: uint32(ponPort), DeviceId: oltM.olt.Id, Type: voltha.Port_PON_OLT}
134 oltM.olt.Ports = append(oltM.olt.Ports, &p)
135 }
136
137 // Wait for onu registration
138 for onuReg := range oltM.oltChnl {
139 if onuReg.onu == nil {
140 // All onu has registered - exit the loop
141 break
142 }
143 oltM.registerOnu(onuReg.onu, onuReg.oltPonNo, onuReg.onuPonNo)
144 }
145 // Inform the logical device manager we are done
146 oltM.logicalDeviceMgr.ldChnl <- portRegistration{port: nil}
147}
148
149func (oltM *oltManager) registerOnu(onu *voltha.Device, oltPonNo uint32, onuPonNo uint32) {
150 // Update the olt pon peers
151 for _, port := range oltM.olt.Ports {
152 if port.Type == voltha.Port_PON_OLT && port.PortNo == oltPonNo {
153 port.Peers = append(port.Peers, &voltha.Port_PeerPort{DeviceId: onu.Id, PortNo: onuPonNo})
154 }
155 }
156 // For each uni port on the ONU trigger the creation of a logical port
157 for _, port := range onu.Ports {
158 if port.Type == voltha.Port_ETHERNET_UNI {
159 oltM.logicalDeviceMgr.ldChnl <- portRegistration{port: port, rootPort: false}
160 }
161 }
162}
163
164type onuManager struct {
165 oltMgr *oltManager
166 numOnus int
167 numUnisPerOnu int
168 startingUniPortNo int
169 numGetDeviceInvoked int
170 numGetDeviceInvokedLock sync.RWMutex
171 deviceLock sync.RWMutex
172 onus []*voltha.Device
173}
174
175func newOnuManager(oltMgr *oltManager, numOnus int, numUnisPerOnu int, startingUniPortNo int) *onuManager {
176 return &onuManager{
177 oltMgr: oltMgr,
178 numOnus: numOnus,
179 numUnisPerOnu: numUnisPerOnu,
180 startingUniPortNo: startingUniPortNo,
181 onus: make([]*voltha.Device, 0),
182 }
183}
184
185func (onuM *onuManager) start(startingOltPeerPortNo int, numPonPortOnOlt int) {
186 var wg sync.WaitGroup
187 for oltPonNo := startingOltPeerPortNo; oltPonNo < startingOltPeerPortNo+numPonPortOnOlt; oltPonNo++ {
188 for i := 0; i < onuM.numOnus; i++ {
189 wg.Add(1)
190 go func(idx int, oltPonNum int) {
191 var onu *voltha.Device
192 defer wg.Done()
193 id := fmt.Sprintf("%d-onu-%d", oltPonNum, idx)
194 onu = &voltha.Device{Id: id, ParentId: onuM.oltMgr.olt.Id, ParentPortNo: uint32(oltPonNum)}
195 ponPort := &voltha.Port{Label: fmt.Sprintf("%s:pon-%d", onu.Id, idx), PortNo: 1, DeviceId: onu.Id, Type: voltha.Port_PON_ONU}
196 ponPort.Peers = make([]*voltha.Port_PeerPort, 0)
197 peerPort := voltha.Port_PeerPort{DeviceId: onuM.oltMgr.olt.Id, PortNo: uint32(oltPonNum)}
198 ponPort.Peers = append(ponPort.Peers, &peerPort)
199 onu.Ports = make([]*voltha.Port, 0)
200 onu.Ports = append(onu.Ports, ponPort)
201 for j := onuM.startingUniPortNo; j < onuM.numUnisPerOnu+onuM.startingUniPortNo; j++ {
202 uniPort := &voltha.Port{Label: fmt.Sprintf("%s:uni-%d", onu.Id, j), PortNo: uint32(j), DeviceId: onu.Id, Type: voltha.Port_ETHERNET_UNI}
203 onu.Ports = append(onu.Ports, uniPort)
204 }
205 onuM.deviceLock.Lock()
206 onuM.onus = append(onuM.onus, onu)
207 onuM.deviceLock.Unlock()
208 onuM.oltMgr.oltChnl <- onuRegistration{
209 onu: onu,
210 oltPonNo: uint32(oltPonNum),
211 onuPonNo: 1,
212 }
213 }(i, oltPonNo)
214 }
215 }
216 wg.Wait()
217 //send an empty device to indicate the end of onu registration
218 onuM.oltMgr.oltChnl <- onuRegistration{
219 onu: nil,
220 oltPonNo: 0,
221 onuPonNo: 1,
222 }
223}
224
225func (onuM *onuManager) getOnu(deviceID string) *voltha.Device {
226 onuM.deviceLock.Lock()
227 defer onuM.deviceLock.Unlock()
228 for _, onu := range onuM.onus {
229 if onu.Id == deviceID {
230 return onu
231 }
232 }
233 return nil
234}
235
khenaidoo0db4c812020-05-27 15:27:30 -0400236func (onuM *onuManager) GetDeviceHelper(ctx context.Context, id string) (*voltha.Device, error) {
237 if ctx.Value(testSetupPhase) != true {
238 onuM.numGetDeviceInvokedLock.Lock()
239 onuM.numGetDeviceInvoked++
240 onuM.numGetDeviceInvokedLock.Unlock()
241 }
khenaidoo820197c2020-02-13 16:35:33 -0500242 if id == oltDeviceID {
243 return onuM.oltMgr.olt, nil
244 }
245 if onu := onuM.getOnu(id); onu != nil {
246 return onu, nil
247 }
248 return nil, errors.New("not-found")
249}
250
251func TestDeviceRoutes_ComputeRoutes(t *testing.T) {
252 numNNIPort := 2
253 numPonPortOnOlt := 8
khenaidoo0db4c812020-05-27 15:27:30 -0400254 numOnuPerOltPonPort := 256
khenaidoo820197c2020-02-13 16:35:33 -0500255 numUniPerOnu := 4
256 done := make(chan struct{})
257
258 fmt.Println(fmt.Sprintf("Test: Computing all routes. LogicalPorts:%d, NNI:%d, Pon/OLT:%d, ONU/Pon:%d, Uni/Onu:%d",
259 numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, numNNIPort, numPonPortOnOlt, numOnuPerOltPonPort, numUniPerOnu))
260
261 // Create all the devices and logical device before computing the routes in one go
262 ld := &voltha.LogicalDevice{Id: logicalDeviceID}
263 ldMgrChnl := make(chan portRegistration, numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu)
264 ldMgr := newLogicalDeviceManager(ld, ldMgrChnl, numNNIPort+numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, done)
265 oltMgrChnl := make(chan onuRegistration, numPonPortOnOlt*numOnuPerOltPonPort)
266 oltMgr := newOltManager(oltDeviceID, ldMgr, numNNIPort, numPonPortOnOlt, oltMgrChnl)
267 onuMgr := newOnuManager(oltMgr, numOnuPerOltPonPort, numUniPerOnu, 2)
268 getDevice := onuMgr.GetDeviceHelper
269 // Start the managers. Only the devices are created. No routes will be built.
270 go ldMgr.start(getDevice, false)
271 go oltMgr.start()
272 go onuMgr.start(numNNIPort+1, numPonPortOnOlt)
273
274 // Wait for all the devices to be created
275 <-done
276 close(oltMgrChnl)
277 close(ldMgrChnl)
278
279 // Computes the routes
280 start := time.Now()
281 err := ldMgr.deviceRoutes.ComputeRoutes(context.TODO(), ldMgr.logicalDevice.Ports)
282 assert.Nil(t, err)
283
284 // Validate the routes are up to date
khenaidoo0db4c812020-05-27 15:27:30 -0400285 assert.True(t, ldMgr.deviceRoutes.isUpToDate(ld))
khenaidoo820197c2020-02-13 16:35:33 -0500286
287 // Validate the expected number of routes
288 assert.EqualValues(t, 2*numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, len(ldMgr.deviceRoutes.Routes))
289
290 // Validate the root ports
291 for _, port := range ldMgr.logicalDevice.Ports {
292 assert.Equal(t, port.RootPort, ldMgr.deviceRoutes.IsRootPort(port.OfpPort.PortNo))
293 }
294 fmt.Println(fmt.Sprintf("Total Time:%dms, Total Routes:%d NumGetDeviceInvoked:%d", time.Since(start)/time.Millisecond, len(ldMgr.deviceRoutes.Routes), onuMgr.numGetDeviceInvoked))
295}
296
297func TestDeviceRoutes_AddPort(t *testing.T) {
298 numNNIPort := 2
khenaidoo0db4c812020-05-27 15:27:30 -0400299 numPonPortOnOlt := 16
300 numOnuPerOltPonPort := 256
khenaidoo820197c2020-02-13 16:35:33 -0500301 numUniPerOnu := 4
302 done := make(chan struct{})
303
304 fmt.Println(fmt.Sprintf("Test: Computing all routes. LogicalPorts:%d, NNI:%d, Pon/OLT:%d, ONU/Pon:%d, Uni/Onu:%d",
305 numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, numNNIPort, numPonPortOnOlt, numOnuPerOltPonPort, numUniPerOnu))
306
307 start := time.Now()
308 // Create all the devices and logical device before computing the routes in one go
309 ld := &voltha.LogicalDevice{Id: logicalDeviceID}
310 ldMgrChnl := make(chan portRegistration, numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu)
311 ldMgr := newLogicalDeviceManager(ld, ldMgrChnl, numNNIPort+numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, done)
312 oltMgrChnl := make(chan onuRegistration, numPonPortOnOlt*numOnuPerOltPonPort)
313 oltMgr := newOltManager(oltDeviceID, ldMgr, numNNIPort, numPonPortOnOlt, oltMgrChnl)
314 onuMgr := newOnuManager(oltMgr, numOnuPerOltPonPort, numUniPerOnu, 2)
315 getDevice := onuMgr.GetDeviceHelper
316 // Start the managers and trigger the routes to be built as the logical ports become available
317 go ldMgr.start(getDevice, true)
318 go oltMgr.start()
319 go onuMgr.start(numNNIPort+1, numPonPortOnOlt)
320
321 // Wait for all the devices to be created and routes created
322 <-done
323 close(oltMgrChnl)
324 close(ldMgrChnl)
325
326 ldMgr.deviceRoutes.Print()
327
328 // Validate the routes are up to date
khenaidoo0db4c812020-05-27 15:27:30 -0400329 assert.True(t, ldMgr.deviceRoutes.isUpToDate(ld))
khenaidoo820197c2020-02-13 16:35:33 -0500330
331 // Validate the expected number of routes
332 assert.EqualValues(t, 2*numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, len(ldMgr.deviceRoutes.Routes))
333
334 // Validate the root ports
335 for _, port := range ldMgr.logicalDevice.Ports {
336 assert.Equal(t, port.RootPort, ldMgr.deviceRoutes.IsRootPort(port.OfpPort.PortNo))
337 }
338
339 fmt.Println(fmt.Sprintf("Total Time:%dms, Total Routes:%d NumGetDeviceInvoked:%d", time.Since(start)/time.Millisecond, len(ldMgr.deviceRoutes.Routes), onuMgr.numGetDeviceInvoked))
340}
341
342func TestDeviceRoutes_compareRoutesGeneration(t *testing.T) {
343 numNNIPort := 2
344 numPonPortOnOlt := 8
345 numOnuPerOltPonPort := 32
346 numUniPerOnu := 4
347 done := make(chan struct{})
348
349 fmt.Println(fmt.Sprintf("Test: Computing all routes. LogicalPorts:%d, NNI:%d, Pon/OLT:%d, ONU/Pon:%d, Uni/Onu:%d",
350 numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, numNNIPort, numPonPortOnOlt, numOnuPerOltPonPort, numUniPerOnu))
351
352 // Create all the devices and logical device before computing the routes in one go
353 ld1 := &voltha.LogicalDevice{Id: logicalDeviceID}
354 ldMgrChnl1 := make(chan portRegistration, numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu)
355 ldMgr1 := newLogicalDeviceManager(ld1, ldMgrChnl1, numNNIPort+numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, done)
356 oltMgrChnl1 := make(chan onuRegistration, numPonPortOnOlt*numOnuPerOltPonPort)
357 oltMgr1 := newOltManager(oltDeviceID, ldMgr1, numNNIPort, numPonPortOnOlt, oltMgrChnl1)
358 onuMgr1 := newOnuManager(oltMgr1, numOnuPerOltPonPort, numUniPerOnu, 2)
359 getDevice := onuMgr1.GetDeviceHelper
360 // Start the managers. Only the devices are created. No routes will be built.
361 go ldMgr1.start(getDevice, false)
362 go oltMgr1.start()
363 go onuMgr1.start(numNNIPort+1, numPonPortOnOlt)
364
365 // Wait for all the devices to be created
366 <-done
367 close(oltMgrChnl1)
368 close(ldMgrChnl1)
369
370 err := ldMgr1.deviceRoutes.ComputeRoutes(context.TODO(), ldMgr1.logicalDevice.Ports)
371 assert.Nil(t, err)
372
373 routesGeneratedAllAtOnce := ldMgr1.deviceRoutes.Routes
374
375 done = make(chan struct{})
376 // Create all the devices and logical device before computing the routes in one go
377 ld2 := &voltha.LogicalDevice{Id: logicalDeviceID}
378 ldMgrChnl2 := make(chan portRegistration, numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu)
379 ldMgr2 := newLogicalDeviceManager(ld2, ldMgrChnl2, numNNIPort+numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, done)
380 oltMgrChnl2 := make(chan onuRegistration, numPonPortOnOlt*numOnuPerOltPonPort)
381 oltMgr2 := newOltManager(oltDeviceID, ldMgr2, numNNIPort, numPonPortOnOlt, oltMgrChnl2)
382 onuMgr2 := newOnuManager(oltMgr2, numOnuPerOltPonPort, numUniPerOnu, 2)
383 // Start the managers. Only the devices are created. No routes will be built.
384 go ldMgr2.start(getDevice, true)
385 go oltMgr2.start()
386 go onuMgr2.start(numNNIPort+1, numPonPortOnOlt)
387
388 // Wait for all the devices to be created
389 <-done
390 close(oltMgrChnl2)
391 close(ldMgrChnl2)
392
393 routesGeneratedPerPort := ldMgr1.deviceRoutes.Routes
394 assert.True(t, isEqual(routesGeneratedAllAtOnce, routesGeneratedPerPort))
395}
396
397func TestDeviceRoutes_reverseRoute(t *testing.T) {
398 // Test the typical use case - 2 hops in a route
399 route := make([]Hop, 2)
400 route[0].DeviceID = "d1"
401 route[0].Ingress = 1
402 route[0].Egress = 2
403 route[1].DeviceID = "d2"
404 route[1].Ingress = 10
405 route[1].Egress = 15
406
407 reverseRoute := getReverseRoute(route)
408 assert.Equal(t, 2, len(reverseRoute))
409 assert.Equal(t, "d2", reverseRoute[0].DeviceID)
410 assert.Equal(t, "d1", reverseRoute[1].DeviceID)
411 assert.Equal(t, uint32(15), reverseRoute[0].Ingress)
412 assert.Equal(t, uint32(10), reverseRoute[0].Egress)
413 assert.Equal(t, uint32(2), reverseRoute[1].Ingress)
414 assert.Equal(t, uint32(1), reverseRoute[1].Egress)
415
416 fmt.Println("Reverse of two hops successful.")
417
418 //Test 3 hops in a route
419 route = make([]Hop, 3)
420 route[0].DeviceID = "d1"
421 route[0].Ingress = 1
422 route[0].Egress = 2
423 route[1].DeviceID = "d2"
424 route[1].Ingress = 10
425 route[1].Egress = 15
426 route[2].DeviceID = "d3"
427 route[2].Ingress = 20
428 route[2].Egress = 25
429 reverseRoute = getReverseRoute(route)
430 assert.Equal(t, 3, len(reverseRoute))
431 assert.Equal(t, "d3", reverseRoute[0].DeviceID)
432 assert.Equal(t, "d2", reverseRoute[1].DeviceID)
433 assert.Equal(t, "d1", reverseRoute[2].DeviceID)
434 assert.Equal(t, uint32(25), reverseRoute[0].Ingress)
435 assert.Equal(t, uint32(20), reverseRoute[0].Egress)
436 assert.Equal(t, uint32(15), reverseRoute[1].Ingress)
437 assert.Equal(t, uint32(10), reverseRoute[1].Egress)
438 assert.Equal(t, uint32(2), reverseRoute[2].Ingress)
439 assert.Equal(t, uint32(1), reverseRoute[2].Egress)
440
441 fmt.Println("Reverse of three hops successful.")
442
443 // Test any number of hops in a route
444 numRoutes := rand.Intn(100)
445 route = make([]Hop, numRoutes)
446 deviceIds := make([]string, numRoutes)
447 ingressNos := make([]uint32, numRoutes)
448 egressNos := make([]uint32, numRoutes)
449 for i := 0; i < numRoutes; i++ {
450 deviceIds[i] = fmt.Sprintf("d-%d", i)
451 ingressNos[i] = rand.Uint32()
452 egressNos[i] = rand.Uint32()
453 }
454 for i := 0; i < numRoutes; i++ {
455 route[i].DeviceID = deviceIds[i]
456 route[i].Ingress = ingressNos[i]
457 route[i].Egress = egressNos[i]
458 }
459 reverseRoute = getReverseRoute(route)
460 assert.Equal(t, numRoutes, len(reverseRoute))
461 for i, j := 0, numRoutes-1; j >= 0; i, j = i+1, j-1 {
462 assert.Equal(t, deviceIds[j], reverseRoute[i].DeviceID)
463 assert.Equal(t, egressNos[j], reverseRoute[i].Ingress)
464 assert.Equal(t, ingressNos[j], reverseRoute[i].Egress)
465 }
466
467 fmt.Println(fmt.Sprintf("Reverse of %d hops successful.", numRoutes))
468
469 reverseOfReverse := getReverseRoute(reverseRoute)
470 assert.Equal(t, route, reverseOfReverse)
471 fmt.Println("Reverse of reverse successful.")
472}
473
474func isEqual(routes1 map[PathID][]Hop, routes2 map[PathID][]Hop) bool {
475 if routes1 == nil && routes2 == nil {
476 return true
477 }
478 if (routes1 == nil && routes2 != nil) || (routes2 == nil && routes1 != nil) {
479 return false
480 }
481 if len(routes1) != len(routes2) {
482 return false
483 }
484 for routeID1, routeHop1 := range routes1 {
485 found := false
486 for routeID2, routeHop2 := range routes2 {
487 if routeID1 == routeID2 {
488 if !reflect.DeepEqual(routeHop1, routeHop2) {
489 return false
490 }
491 found = true
492 break
493 }
494 }
495 if !found {
496 return false
497 }
498 }
499 return true
500}