blob: 4fbda0e7144b92de64e35768406ea7efe9e581ee [file] [log] [blame]
/*
* Copyright 2018-2023 Open Networking Foundation (ONF) and the ONF Contributors
*
* 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 route
import (
"context"
"errors"
"fmt"
"math/rand"
"reflect"
"strings"
"sync"
"testing"
"time"
"github.com/opencord/voltha-protos/v5/go/openflow_13"
"github.com/opencord/voltha-protos/v5/go/voltha"
"github.com/stretchr/testify/assert"
)
const (
logicalDeviceID = "ld"
oltDeviceID = "olt"
)
const testSetupPhase contextKey = "testSetupPhase"
type contextKey string
// portRegistration is a message sent from an OLT device to a logical device to create a logical port
type portRegistration struct {
port *voltha.Port
rootPort bool
}
// onuRegistration is a message sent from an ONU device to an OLT device to register an ONU
type onuRegistration struct {
onuID string
onuPorts map[uint32]*voltha.Port
oltPonNo uint32
onuPonNo uint32
}
type logicalDeviceManager struct {
logicalDeviceID string
rootDeviceID string
ports map[uint32]*voltha.LogicalPort
deviceRoutes *DeviceRoutes
ldChnl chan portRegistration
numLogicalPorts int
done chan struct{}
}
func newLogicalDeviceManager(ld *voltha.LogicalDevice, rootDeviceID string, ch chan portRegistration, totalLogicalPorts int, done chan struct{}) *logicalDeviceManager {
return &logicalDeviceManager{
logicalDeviceID: ld.Id,
rootDeviceID: rootDeviceID,
ports: make(map[uint32]*voltha.LogicalPort),
ldChnl: ch,
numLogicalPorts: totalLogicalPorts,
done: done,
}
}
func (ldM *logicalDeviceManager) start(ctx context.Context, listDevicePorts listDevicePortsFunc, buildRoutes bool) {
ldM.deviceRoutes = NewDeviceRoutes(ldM.logicalDeviceID, ldM.rootDeviceID, listDevicePorts)
ofpPortNo := uint32(1)
for portReg := range ldM.ldChnl {
if portReg.port == nil {
// End of registration - exit loop
break
}
lp := &voltha.LogicalPort{
Id: portReg.port.Label,
OfpPort: &openflow_13.OfpPort{PortNo: ofpPortNo},
DeviceId: portReg.port.DeviceId,
DevicePortNo: portReg.port.PortNo,
RootPort: portReg.rootPort,
}
ldM.ports[lp.DevicePortNo] = lp
if buildRoutes {
devicePorts, err := listDevicePorts(context.WithValue(context.Background(), testSetupPhase, true), lp.DeviceId)
if err != nil {
fmt.Println("Error when getting device:", lp.DeviceId, err)
}
if err := ldM.deviceRoutes.AddPort(context.Background(), lp, lp.DeviceId, devicePorts, ldM.ports); err != nil && !strings.Contains(err.Error(), "code = FailedPrecondition") {
fmt.Println("(Error when adding port:", lp, len(ldM.ports), err)
}
}
ofpPortNo++
}
// Inform the caller we are now done
ldM.done <- struct{}{}
}
type oltManager struct {
oltID string
oltPorts map[uint32]*voltha.Port
logicalDeviceMgr *logicalDeviceManager
numNNIPort int
numPonPortOnOlt int
oltChnl chan onuRegistration
}
func newOltManager(oltDeviceID string, ldMgr *logicalDeviceManager, numNNIPort int, numPonPortOnOlt int, ch chan onuRegistration) *oltManager {
return &oltManager{
oltID: oltDeviceID, // ParentId: ldMgr.logicalDeviceID, Root: true},
oltPorts: make(map[uint32]*voltha.Port),
logicalDeviceMgr: ldMgr,
numNNIPort: numNNIPort,
numPonPortOnOlt: numPonPortOnOlt,
oltChnl: ch,
}
}
func (oltM *oltManager) start() {
// Setup the OLT nni ports and trigger the nni ports creation
for nniPort := 1; nniPort < oltM.numNNIPort+1; nniPort++ {
p := &voltha.Port{Label: fmt.Sprintf("nni-%d", nniPort), PortNo: uint32(nniPort), DeviceId: oltM.oltID, Type: voltha.Port_ETHERNET_NNI}
oltM.oltPorts[p.PortNo] = p
oltM.logicalDeviceMgr.ldChnl <- portRegistration{port: p, rootPort: true}
}
// Create OLT pon ports
for ponPort := oltM.numNNIPort + 1; ponPort < oltM.numPonPortOnOlt+oltM.numNNIPort+1; ponPort++ {
p := &voltha.Port{PortNo: uint32(ponPort), DeviceId: oltM.oltID, Type: voltha.Port_PON_OLT}
oltM.oltPorts[p.PortNo] = p
}
// Wait for onu registration
for onuReg := range oltM.oltChnl {
if onuReg.onuPorts == nil {
// All onu has registered - exit the loop
break
}
oltM.registerOnu(onuReg.onuID, onuReg.onuPorts, onuReg.oltPonNo, onuReg.onuPonNo)
}
// Inform the logical device manager we are done
oltM.logicalDeviceMgr.ldChnl <- portRegistration{port: nil}
}
func (oltM *oltManager) registerOnu(onuID string, onuPorts map[uint32]*voltha.Port, oltPonNo uint32, onuPonNo uint32) {
// Update the olt pon peers
for _, port := range oltM.oltPorts {
if port.Type == voltha.Port_PON_OLT && port.PortNo == oltPonNo {
port.Peers = append(port.Peers, &voltha.Port_PeerPort{DeviceId: onuID, PortNo: onuPonNo})
}
}
// For each uni port on the ONU trigger the creation of a logical port
for _, port := range onuPorts {
if port.Type == voltha.Port_ETHERNET_UNI {
oltM.logicalDeviceMgr.ldChnl <- portRegistration{port: port, rootPort: false}
}
}
}
type onuManager struct {
oltMgr *oltManager
numOnus int
numUnisPerOnu int
startingUniPortNo int
numGetDeviceInvoked int
numGetDeviceInvokedLock sync.RWMutex
deviceLock sync.RWMutex
onus []*voltha.Device
onuPorts map[string]map[uint32]*voltha.Port
}
func newOnuManager(oltMgr *oltManager, numOnus int, numUnisPerOnu int, startingUniPortNo int) *onuManager {
return &onuManager{
oltMgr: oltMgr,
numOnus: numOnus,
numUnisPerOnu: numUnisPerOnu,
startingUniPortNo: startingUniPortNo,
onus: make([]*voltha.Device, 0),
onuPorts: make(map[string]map[uint32]*voltha.Port),
}
}
func (onuM *onuManager) start(startingOltPeerPortNo int, numPonPortOnOlt int) {
var wg sync.WaitGroup
for oltPonNo := startingOltPeerPortNo; oltPonNo < startingOltPeerPortNo+numPonPortOnOlt; oltPonNo++ {
for onuID := 0; onuID < onuM.numOnus; onuID++ {
wg.Add(1)
go func(onuID int, oltPonNum int) {
var onu *voltha.Device
defer wg.Done()
id := fmt.Sprintf("%d-onu-%d", oltPonNum, onuID)
onu = &voltha.Device{Id: id, ParentId: onuM.oltMgr.oltID, ParentPortNo: uint32(oltPonNum)}
ponPort := &voltha.Port{Label: fmt.Sprintf("%s:pon-%d", onu.Id, onuID), PortNo: 1, DeviceId: onu.Id, Type: voltha.Port_PON_ONU}
ponPort.Peers = make([]*voltha.Port_PeerPort, 0)
peerPort := voltha.Port_PeerPort{DeviceId: onuM.oltMgr.oltID, PortNo: uint32(oltPonNum)}
ponPort.Peers = append(ponPort.Peers, &peerPort)
onuPorts := make(map[uint32]*voltha.Port)
onuPorts[ponPort.PortNo] = ponPort
for j := onuM.startingUniPortNo; j < onuM.numUnisPerOnu+onuM.startingUniPortNo; j++ {
uniPort := &voltha.Port{Label: fmt.Sprintf("%s:uni-%d", onu.Id, j), PortNo: uint32(oltPonNum)<<12 + uint32(onuID+1)<<4 + uint32(j), DeviceId: onu.Id, Type: voltha.Port_ETHERNET_UNI}
onuPorts[uniPort.PortNo] = uniPort
}
onuM.deviceLock.Lock()
onuM.onus = append(onuM.onus, onu)
onuM.onuPorts[id] = onuPorts
onuM.deviceLock.Unlock()
onuM.oltMgr.oltChnl <- onuRegistration{
onuID: onu.Id,
onuPorts: onuPorts,
oltPonNo: uint32(oltPonNum),
onuPonNo: 1,
}
}(onuID, oltPonNo)
}
}
wg.Wait()
//send an empty device to indicate the end of onu registration
onuM.oltMgr.oltChnl <- onuRegistration{
onuPorts: nil,
oltPonNo: 0,
onuPonNo: 1,
}
}
func (onuM *onuManager) getOnuPorts(deviceID string) (map[uint32]*voltha.Port, bool) {
onuM.deviceLock.Lock()
defer onuM.deviceLock.Unlock()
ports, have := onuM.onuPorts[deviceID]
return ports, have
}
func (onuM *onuManager) ListDevicePortsHelper(ctx context.Context, id string) (map[uint32]*voltha.Port, error) {
if ctx.Value(testSetupPhase) != true {
onuM.numGetDeviceInvokedLock.Lock()
onuM.numGetDeviceInvoked++
onuM.numGetDeviceInvokedLock.Unlock()
}
if id == oltDeviceID {
return onuM.oltMgr.oltPorts, nil
}
if onuPorts, have := onuM.getOnuPorts(id); have {
return onuPorts, nil
}
return nil, errors.New("not-found")
}
func TestDeviceRoutes_ComputeRoutes(t *testing.T) {
numNNIPort := 2
numPonPortOnOlt := 8
numOnuPerOltPonPort := 256
numUniPerOnu := 4
done := make(chan struct{})
fmt.Printf("Test: Computing all routes. LogicalPorts:%d, NNI:%d, Pon/OLT:%d, ONU/Pon:%d, Uni/Onu:%d\n",
numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, numNNIPort, numPonPortOnOlt, numOnuPerOltPonPort, numUniPerOnu)
// Create all the devices and logical device before computing the routes in one go
ld := &voltha.LogicalDevice{Id: logicalDeviceID}
ldMgrChnl := make(chan portRegistration, numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu)
ldMgr := newLogicalDeviceManager(ld, oltDeviceID, ldMgrChnl, numNNIPort+numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, done)
oltMgrChnl := make(chan onuRegistration, numPonPortOnOlt*numOnuPerOltPonPort)
oltMgr := newOltManager(oltDeviceID, ldMgr, numNNIPort, numPonPortOnOlt, oltMgrChnl)
onuMgr := newOnuManager(oltMgr, numOnuPerOltPonPort, numUniPerOnu, 2)
// Start the managers. Only the devices are created. No routes will be built.
ctx := context.Background()
go ldMgr.start(ctx, onuMgr.ListDevicePortsHelper, false)
go oltMgr.start()
go onuMgr.start(numNNIPort+1, numPonPortOnOlt)
// Wait for all the devices to be created
<-done
close(oltMgrChnl)
close(ldMgrChnl)
// Computes the routes
start := time.Now()
err := ldMgr.deviceRoutes.ComputeRoutes(context.TODO(), ldMgr.ports)
assert.Nil(t, err)
// Validate the routes are up to date
assert.True(t, ldMgr.deviceRoutes.isUpToDate(ldMgr.ports))
// Validate the expected number of routes
assert.EqualValues(t, 2*numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, len(ldMgr.deviceRoutes.Routes))
// Validate the root ports
for _, port := range ldMgr.ports {
assert.Equal(t, port.RootPort, ldMgr.deviceRoutes.IsRootPort(port.OfpPort.PortNo))
}
fmt.Printf(
"Total Time:%dms, Total Routes:%d NumGetDeviceInvoked:%d\n",
time.Since(start)/time.Millisecond,
len(ldMgr.deviceRoutes.Routes),
onuMgr.numGetDeviceInvoked,
)
}
func TestDeviceRoutes_AddPort(t *testing.T) {
numNNIPort := 2
numPonPortOnOlt := 16
numOnuPerOltPonPort := 256
numUniPerOnu := 4
done := make(chan struct{})
fmt.Printf("Test: Computing all routes. LogicalPorts:%d, NNI:%d, Pon/OLT:%d, ONU/Pon:%d, Uni/Onu:%d\n",
numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, numNNIPort, numPonPortOnOlt, numOnuPerOltPonPort, numUniPerOnu)
start := time.Now()
// Create all the devices and logical device before computing the routes in one go
ld := &voltha.LogicalDevice{Id: logicalDeviceID}
ldMgrChnl := make(chan portRegistration, numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu)
ldMgr := newLogicalDeviceManager(ld, oltDeviceID, ldMgrChnl, numNNIPort+numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, done)
oltMgrChnl := make(chan onuRegistration, numPonPortOnOlt*numOnuPerOltPonPort)
oltMgr := newOltManager(oltDeviceID, ldMgr, numNNIPort, numPonPortOnOlt, oltMgrChnl)
onuMgr := newOnuManager(oltMgr, numOnuPerOltPonPort, numUniPerOnu, 2)
getDevice := onuMgr.ListDevicePortsHelper
// Start the managers and trigger the routes to be built as the logical ports become available
ctx := context.Background()
go ldMgr.start(ctx, getDevice, true)
go oltMgr.start()
go onuMgr.start(numNNIPort+1, numPonPortOnOlt)
// Wait for all the devices to be created and routes created
<-done
close(oltMgrChnl)
close(ldMgrChnl)
err := ldMgr.deviceRoutes.Print(ctx)
assert.NoError(t, err)
// Validate the routes are up to date
assert.True(t, ldMgr.deviceRoutes.isUpToDate(ldMgr.ports))
// Validate the expected number of routes
assert.EqualValues(t, 2*numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, len(ldMgr.deviceRoutes.Routes))
// Validate the root ports
for _, port := range ldMgr.ports {
assert.Equal(t, port.RootPort, ldMgr.deviceRoutes.IsRootPort(port.OfpPort.PortNo))
}
fmt.Printf(
"Total Time:%dms, Total Routes:%d NumGetDeviceInvoked:%d\n",
time.Since(start)/time.Millisecond,
len(ldMgr.deviceRoutes.Routes),
onuMgr.numGetDeviceInvoked,
)
}
func TestDeviceRoutes_compareRoutesGeneration(t *testing.T) {
numNNIPort := 2
numPonPortOnOlt := 8
numOnuPerOltPonPort := 32
numUniPerOnu := 4
done := make(chan struct{})
fmt.Printf("Test: Computing all routes. LogicalPorts:%d, NNI:%d, Pon/OLT:%d, ONU/Pon:%d, Uni/Onu:%d\n",
numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, numNNIPort, numPonPortOnOlt, numOnuPerOltPonPort, numUniPerOnu)
// Create all the devices and logical device before computing the routes in one go
ld1 := &voltha.LogicalDevice{Id: logicalDeviceID}
ldMgrChnl1 := make(chan portRegistration, numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu)
ldMgr1 := newLogicalDeviceManager(ld1, oltDeviceID, ldMgrChnl1, numNNIPort+numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, done)
oltMgrChnl1 := make(chan onuRegistration, numPonPortOnOlt*numOnuPerOltPonPort)
oltMgr1 := newOltManager(oltDeviceID, ldMgr1, numNNIPort, numPonPortOnOlt, oltMgrChnl1)
onuMgr1 := newOnuManager(oltMgr1, numOnuPerOltPonPort, numUniPerOnu, 2)
getDevice := onuMgr1.ListDevicePortsHelper
ctx := context.Background()
// Start the managers. Only the devices are created. No routes will be built.
go ldMgr1.start(ctx, getDevice, false)
go oltMgr1.start()
go onuMgr1.start(numNNIPort+1, numPonPortOnOlt)
// Wait for all the devices to be created
<-done
close(oltMgrChnl1)
close(ldMgrChnl1)
err := ldMgr1.deviceRoutes.ComputeRoutes(context.TODO(), ldMgr1.ports)
assert.Nil(t, err)
routesGeneratedAllAtOnce := ldMgr1.deviceRoutes.Routes
done = make(chan struct{})
// Create all the devices and logical device before computing the routes in one go
ld2 := &voltha.LogicalDevice{Id: logicalDeviceID}
ldMgrChnl2 := make(chan portRegistration, numNNIPort*numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu)
ldMgr2 := newLogicalDeviceManager(ld2, oltDeviceID, ldMgrChnl2, numNNIPort+numPonPortOnOlt*numOnuPerOltPonPort*numUniPerOnu, done)
oltMgrChnl2 := make(chan onuRegistration, numPonPortOnOlt*numOnuPerOltPonPort)
oltMgr2 := newOltManager(oltDeviceID, ldMgr2, numNNIPort, numPonPortOnOlt, oltMgrChnl2)
onuMgr2 := newOnuManager(oltMgr2, numOnuPerOltPonPort, numUniPerOnu, 2)
// Start the managers. Only the devices are created. No routes will be built.
go ldMgr2.start(ctx, getDevice, true)
go oltMgr2.start()
go onuMgr2.start(numNNIPort+1, numPonPortOnOlt)
// Wait for all the devices to be created
<-done
close(oltMgrChnl2)
close(ldMgrChnl2)
routesGeneratedPerPort := ldMgr1.deviceRoutes.Routes
assert.True(t, isEqual(routesGeneratedAllAtOnce, routesGeneratedPerPort))
}
func TestDeviceRoutes_reverseRoute(t *testing.T) {
// Test the typical use case - 2 hops in a route
route := make([]Hop, 2)
route[0].DeviceID = "d1"
route[0].Ingress = 1
route[0].Egress = 2
route[1].DeviceID = "d2"
route[1].Ingress = 10
route[1].Egress = 15
reverseRoute := getReverseRoute(route)
assert.Equal(t, 2, len(reverseRoute))
assert.Equal(t, "d2", reverseRoute[0].DeviceID)
assert.Equal(t, "d1", reverseRoute[1].DeviceID)
assert.Equal(t, uint32(15), reverseRoute[0].Ingress)
assert.Equal(t, uint32(10), reverseRoute[0].Egress)
assert.Equal(t, uint32(2), reverseRoute[1].Ingress)
assert.Equal(t, uint32(1), reverseRoute[1].Egress)
fmt.Println("Reverse of two hops successful.")
//Test 3 hops in a route
route = make([]Hop, 3)
route[0].DeviceID = "d1"
route[0].Ingress = 1
route[0].Egress = 2
route[1].DeviceID = "d2"
route[1].Ingress = 10
route[1].Egress = 15
route[2].DeviceID = "d3"
route[2].Ingress = 20
route[2].Egress = 25
reverseRoute = getReverseRoute(route)
assert.Equal(t, 3, len(reverseRoute))
assert.Equal(t, "d3", reverseRoute[0].DeviceID)
assert.Equal(t, "d2", reverseRoute[1].DeviceID)
assert.Equal(t, "d1", reverseRoute[2].DeviceID)
assert.Equal(t, uint32(25), reverseRoute[0].Ingress)
assert.Equal(t, uint32(20), reverseRoute[0].Egress)
assert.Equal(t, uint32(15), reverseRoute[1].Ingress)
assert.Equal(t, uint32(10), reverseRoute[1].Egress)
assert.Equal(t, uint32(2), reverseRoute[2].Ingress)
assert.Equal(t, uint32(1), reverseRoute[2].Egress)
fmt.Println("Reverse of three hops successful.")
// Test any number of hops in a route
numRoutes := rand.Intn(100)
route = make([]Hop, numRoutes)
deviceIds := make([]string, numRoutes)
ingressNos := make([]uint32, numRoutes)
egressNos := make([]uint32, numRoutes)
for i := 0; i < numRoutes; i++ {
deviceIds[i] = fmt.Sprintf("d-%d", i)
ingressNos[i] = rand.Uint32()
egressNos[i] = rand.Uint32()
}
for i := 0; i < numRoutes; i++ {
route[i].DeviceID = deviceIds[i]
route[i].Ingress = ingressNos[i]
route[i].Egress = egressNos[i]
}
reverseRoute = getReverseRoute(route)
assert.Equal(t, numRoutes, len(reverseRoute))
for i, j := 0, numRoutes-1; j >= 0; i, j = i+1, j-1 {
assert.Equal(t, deviceIds[j], reverseRoute[i].DeviceID)
assert.Equal(t, egressNos[j], reverseRoute[i].Ingress)
assert.Equal(t, ingressNos[j], reverseRoute[i].Egress)
}
fmt.Printf("Reverse of %d hops successful.\n", numRoutes)
reverseOfReverse := getReverseRoute(reverseRoute)
assert.Equal(t, route, reverseOfReverse)
fmt.Println("Reverse of reverse successful.")
}
func isEqual(routes1 map[PathID][]Hop, routes2 map[PathID][]Hop) bool {
if routes1 == nil && routes2 == nil {
return true
}
if (routes1 == nil && routes2 != nil) || (routes2 == nil && routes1 != nil) {
return false
}
if len(routes1) != len(routes2) {
return false
}
for routeID1, routeHop1 := range routes1 {
found := false
for routeID2, routeHop2 := range routes2 {
if routeID1 == routeID2 {
if !reflect.DeepEqual(routeHop1, routeHop2) {
return false
}
found = true
break
}
}
if !found {
return false
}
}
return true
}