| /* |
| * Copyright 2020-present Open Networking Foundation |
| * |
| * 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" |
| "github.com/opencord/voltha-lib-go/v3/pkg/log" |
| "github.com/opencord/voltha-protos/v3/go/voltha" |
| "google.golang.org/grpc/codes" |
| "google.golang.org/grpc/status" |
| "sync" |
| ) |
| |
| var ErrNoRoute = errors.New("no route") |
| |
| // Hop represent a route hop |
| type Hop struct { |
| DeviceID string |
| Ingress uint32 |
| Egress uint32 |
| } |
| |
| // PathID is the identification of a route between two logical ports |
| type PathID struct { |
| Ingress uint32 |
| Egress uint32 |
| } |
| |
| type OFPortLink struct { |
| Ingress uint32 |
| Egress uint32 |
| } |
| |
| // GetDeviceFunc returns device function |
| type GetDeviceFunc func(ctx context.Context, id string) (*voltha.Device, error) |
| |
| // DeviceRoutes represent the set of routes between logical ports of a logical device |
| type DeviceRoutes struct { |
| logicalDeviceID string |
| getDeviceFromModel GetDeviceFunc |
| logicalPorts []*voltha.LogicalPort |
| RootPorts map[uint32]uint32 |
| rootPortsLock sync.RWMutex |
| Routes map[PathID][]Hop |
| routeBuildLock sync.RWMutex |
| devicesPonPorts map[string][]*voltha.Port |
| devicesPonPortsLock sync.RWMutex |
| } |
| |
| // NewDeviceRoutes creates device graph instance |
| func NewDeviceRoutes(logicalDeviceID string, getDevice GetDeviceFunc) *DeviceRoutes { |
| var dr DeviceRoutes |
| dr.logicalDeviceID = logicalDeviceID |
| dr.getDeviceFromModel = getDevice |
| dr.RootPorts = make(map[uint32]uint32) |
| dr.Routes = make(map[PathID][]Hop) |
| dr.devicesPonPorts = make(map[string][]*voltha.Port) |
| logger.Debug("new device routes created ...") |
| return &dr |
| } |
| |
| //IsRootPort returns true if the port is a root port on a logical device |
| func (dr *DeviceRoutes) IsRootPort(port uint32) bool { |
| dr.rootPortsLock.RLock() |
| defer dr.rootPortsLock.RUnlock() |
| _, exist := dr.RootPorts[port] |
| return exist |
| } |
| |
| //ComputeRoutes calculates all the routes between the logical ports. This will clear up any existing route |
| func (dr *DeviceRoutes) ComputeRoutes(ctx context.Context, lps []*voltha.LogicalPort) error { |
| dr.routeBuildLock.Lock() |
| defer dr.routeBuildLock.Unlock() |
| |
| logger.Debugw("computing-all-routes", log.Fields{"len-logical-ports": len(lps)}) |
| var err error |
| defer func() { |
| // On error, clear the routes - any flow request or a port add/delete will trigger the rebuild |
| if err != nil { |
| dr.reset() |
| } |
| }() |
| |
| if len(lps) < 2 { |
| return fmt.Errorf("not enough logical port :%w", ErrNoRoute) |
| } |
| |
| dr.reset() |
| dr.logicalPorts = append(dr.logicalPorts, lps...) |
| |
| // Setup the physical ports to logical ports map, the nni ports as well as the root ports map |
| physPortToLogicalPortMap := make(map[string]uint32) |
| nniPorts := make([]*voltha.LogicalPort, 0) |
| for _, lp := range lps { |
| physPortToLogicalPortMap[concatDeviceIDPortID(lp.DeviceId, lp.DevicePortNo)] = lp.OfpPort.PortNo |
| if lp.RootPort { |
| nniPorts = append(nniPorts, lp) |
| dr.RootPorts[lp.OfpPort.PortNo] = lp.OfpPort.PortNo |
| } |
| } |
| if len(nniPorts) == 0 { |
| return fmt.Errorf("no nni port :%w", ErrNoRoute) |
| } |
| var rootDevice *voltha.Device |
| var childDevice *voltha.Device |
| var copyFromNNIPort *voltha.LogicalPort |
| for idx, nniPort := range nniPorts { |
| if idx == 0 { |
| copyFromNNIPort = nniPort |
| } else if len(dr.Routes) > 0 { |
| dr.copyFromExistingNNIRoutes(nniPort, copyFromNNIPort) |
| return nil |
| } |
| // Get root device |
| rootDevice, err = dr.getDevice(ctx, nniPort.DeviceId) |
| if err != nil { |
| return err |
| } |
| if len(rootDevice.Ports) == 0 { |
| err = status.Errorf(codes.FailedPrecondition, "no-port-%s", rootDevice.Id) |
| return err |
| } |
| for _, rootDevicePort := range rootDevice.Ports { |
| if rootDevicePort.Type == voltha.Port_PON_OLT { |
| logger.Debugw("peers", log.Fields{"root-device-id": rootDevice.Id, "port-no": rootDevicePort.PortNo, "len-peers": len(rootDevicePort.Peers)}) |
| for _, rootDevicePeer := range rootDevicePort.Peers { |
| childDevice, err = dr.getDevice(ctx, rootDevicePeer.DeviceId) |
| if err != nil { |
| return err |
| } |
| childPonPorts := dr.getDevicePonPorts(childDevice.Id, nniPort.DeviceId) |
| if len(childPonPorts) < 1 { |
| err = status.Errorf(codes.Aborted, "no-child-pon-port-%s", childDevice.Id) |
| return err |
| } |
| // We use the first PON port on the ONU whose parent is the root device. |
| childPonPort := childPonPorts[0].PortNo |
| for _, childDevicePort := range childDevice.Ports { |
| if childDevicePort.Type == voltha.Port_ETHERNET_UNI { |
| childLogicalPort, exist := physPortToLogicalPortMap[concatDeviceIDPortID(childDevice.Id, childDevicePort.PortNo)] |
| if !exist { |
| // This can happen if this logical port has not been created yet for that device |
| continue |
| } |
| dr.Routes[PathID{Ingress: nniPort.OfpPort.PortNo, Egress: childLogicalPort}] = []Hop{ |
| {DeviceID: rootDevice.Id, Ingress: nniPort.DevicePortNo, Egress: rootDevicePort.PortNo}, |
| {DeviceID: childDevice.Id, Ingress: childPonPort, Egress: childDevicePort.PortNo}, |
| } |
| dr.Routes[PathID{Ingress: childLogicalPort, Egress: nniPort.OfpPort.PortNo}] = getReverseRoute( |
| dr.Routes[PathID{Ingress: nniPort.OfpPort.PortNo, Egress: childLogicalPort}]) |
| } |
| } |
| } |
| } |
| } |
| } |
| return nil |
| } |
| |
| // verifyPrecondition verify whether the preconditions are met to proceed with addition of the new logical port |
| func (dr *DeviceRoutes) addPortAndVerifyPrecondition(lp *voltha.LogicalPort) error { |
| var exist, nniLogicalPortExist, uniLogicalPortExist bool |
| for _, existingLogicalPort := range dr.logicalPorts { |
| nniLogicalPortExist = nniLogicalPortExist || existingLogicalPort.RootPort |
| uniLogicalPortExist = uniLogicalPortExist || !existingLogicalPort.RootPort |
| exist = exist || existingLogicalPort.OfpPort.PortNo == lp.OfpPort.PortNo |
| if nniLogicalPortExist && uniLogicalPortExist && exist { |
| break |
| } |
| } |
| if !exist { |
| dr.logicalPorts = append(dr.logicalPorts, lp) |
| nniLogicalPortExist = nniLogicalPortExist || lp.RootPort |
| uniLogicalPortExist = uniLogicalPortExist || !lp.RootPort |
| } |
| |
| // If we do not have both NNI and UNI ports then return an error |
| if !(nniLogicalPortExist && uniLogicalPortExist) { |
| fmt.Println("errors", nniLogicalPortExist, uniLogicalPortExist) |
| return status.Error(codes.FailedPrecondition, "no-uni-and-nni-ports-combination") |
| } |
| return nil |
| } |
| |
| // AddPort augments the current set of routes with new routes corresponding to the logical port "lp". If the routes have |
| // not been built yet then use logical port "lps" to compute all current routes (lps includes lp) |
| func (dr *DeviceRoutes) AddPort(ctx context.Context, lp *voltha.LogicalPort, lps []*voltha.LogicalPort) error { |
| logger.Debugw("add-port-to-routes", log.Fields{"port": lp, "len-logical-ports": len(lps)}) |
| |
| dr.routeBuildLock.Lock() |
| if len(dr.Routes) == 0 { |
| dr.routeBuildLock.Unlock() |
| return dr.ComputeRoutes(ctx, lps) |
| } |
| |
| // A set of routes exists |
| if err := dr.addPortAndVerifyPrecondition(lp); err != nil { |
| dr.reset() |
| dr.routeBuildLock.Unlock() |
| return err |
| } |
| |
| defer dr.routeBuildLock.Unlock() |
| // Update the set of root ports, if applicable |
| if lp.RootPort { |
| dr.RootPorts[lp.OfpPort.PortNo] = lp.OfpPort.PortNo |
| } |
| |
| var copyFromNNIPort *voltha.LogicalPort |
| // Setup the physical ports to logical ports map |
| nniPorts := make([]*voltha.LogicalPort, 0) |
| for _, lport := range dr.logicalPorts { |
| if lport.RootPort { |
| nniPorts = append(nniPorts, lport) |
| if copyFromNNIPort == nil && lport.OfpPort.PortNo != lp.OfpPort.PortNo { |
| copyFromNNIPort = lport |
| } |
| } |
| } |
| |
| if copyFromNNIPort == nil { |
| // Trying to add the same NNI port. Just return |
| return nil |
| } |
| |
| // Adding NNI Port? If we are here we already have an NNI port with a set of routes. Just copy the existing |
| // routes using an existing NNI port |
| if lp.RootPort { |
| dr.copyFromExistingNNIRoutes(lp, copyFromNNIPort) |
| return nil |
| } |
| |
| // Adding a UNI port |
| for _, nniPort := range nniPorts { |
| childPonPorts := dr.getDevicePonPorts(lp.DeviceId, nniPort.DeviceId) |
| if len(childPonPorts) == 0 || len(childPonPorts[0].Peers) == 0 { |
| // Ports may not have been cached yet - get the device info which sets the PON port cache |
| if _, err := dr.getDevice(ctx, lp.DeviceId); err != nil { |
| dr.reset() |
| return err |
| } |
| childPonPorts = dr.getDevicePonPorts(lp.DeviceId, nniPort.DeviceId) |
| if len(childPonPorts) == 0 || len(childPonPorts[0].Peers) == 0 { |
| dr.reset() |
| return status.Errorf(codes.FailedPrecondition, "no-pon-ports-%s", lp.DeviceId) |
| } |
| } |
| // We use the first PON port on the child device |
| childPonPort := childPonPorts[0] |
| dr.Routes[PathID{Ingress: nniPort.OfpPort.PortNo, Egress: lp.OfpPort.PortNo}] = []Hop{ |
| {DeviceID: nniPort.DeviceId, Ingress: nniPort.DevicePortNo, Egress: childPonPort.Peers[0].PortNo}, |
| {DeviceID: lp.DeviceId, Ingress: childPonPort.PortNo, Egress: lp.DevicePortNo}, |
| } |
| dr.Routes[PathID{Ingress: lp.OfpPort.PortNo, Egress: nniPort.OfpPort.PortNo}] = getReverseRoute( |
| dr.Routes[PathID{Ingress: nniPort.OfpPort.PortNo, Egress: lp.OfpPort.PortNo}]) |
| } |
| return nil |
| } |
| |
| // Print prints routes |
| func (dr *DeviceRoutes) Print() error { |
| logger.Debugw("Print", log.Fields{"logical-device-id": dr.logicalDeviceID, "logical-ports": dr.logicalPorts}) |
| if logger.V(log.DebugLevel) { |
| output := "" |
| routeNumber := 1 |
| for k, v := range dr.Routes { |
| key := fmt.Sprintf("LP:%d->LP:%d", k.Ingress, k.Egress) |
| val := "" |
| for _, i := range v { |
| val += fmt.Sprintf("{%d->%s->%d},", i.Ingress, i.DeviceID, i.Egress) |
| } |
| val = val[:len(val)-1] |
| output += fmt.Sprintf("%d:{%s=>%s} ", routeNumber, key, fmt.Sprintf("[%s]", val)) |
| routeNumber++ |
| } |
| if len(dr.Routes) == 0 { |
| logger.Debugw("no-routes-found", log.Fields{"logical-device-id": dr.logicalDeviceID}) |
| } else { |
| logger.Debugw("graph_routes", log.Fields{"lDeviceId": dr.logicalDeviceID, "Routes": output}) |
| } |
| } |
| return nil |
| } |
| |
| // IsUpToDate returns true if device is up to date |
| func (dr *DeviceRoutes) IsUpToDate(ld *voltha.LogicalDevice) bool { |
| dr.routeBuildLock.Lock() |
| defer dr.routeBuildLock.Unlock() |
| numNNI, numUNI := 0, 0 |
| if ld != nil { |
| if len(dr.logicalPorts) != len(ld.Ports) { |
| return false |
| } |
| numNNI = len(dr.RootPorts) |
| numUNI = len(ld.Ports) - numNNI |
| } |
| return len(dr.Routes) == numNNI*numUNI*2 |
| } |
| |
| // getDevicePonPorts returns all the PON ports of a device whose peer device ID is peerDeviceID |
| func (dr *DeviceRoutes) getDevicePonPorts(deviceID string, peerDeviceID string) []*voltha.Port { |
| dr.devicesPonPortsLock.RLock() |
| defer dr.devicesPonPortsLock.RUnlock() |
| ponPorts := make([]*voltha.Port, 0) |
| ports, exist := dr.devicesPonPorts[deviceID] |
| if !exist { |
| return ponPorts |
| } |
| //fmt.Println("getDevicePonPorts", deviceID, peerDeviceID, ports) |
| for _, port := range ports { |
| for _, peer := range port.Peers { |
| if peer.DeviceId == peerDeviceID { |
| ponPorts = append(ponPorts, port) |
| } |
| } |
| } |
| return ponPorts |
| } |
| |
| //getDevice returns the from the model and updates the PON ports map of that device. |
| func (dr *DeviceRoutes) getDevice(ctx context.Context, deviceID string) (*voltha.Device, error) { |
| device, err := dr.getDeviceFromModel(ctx, deviceID) |
| if err != nil { |
| logger.Errorw("device-not-found", log.Fields{"deviceId": deviceID, "error": err}) |
| return nil, err |
| } |
| dr.devicesPonPortsLock.Lock() |
| defer dr.devicesPonPortsLock.Unlock() |
| for _, port := range device.Ports { |
| if port.Type == voltha.Port_PON_ONU || port.Type == voltha.Port_PON_OLT { |
| dr.devicesPonPorts[device.Id] = append(dr.devicesPonPorts[device.Id], port) |
| } |
| } |
| return device, nil |
| } |
| |
| //copyFromExistingNNIRoutes copies routes from an existing set of NNI routes |
| func (dr *DeviceRoutes) copyFromExistingNNIRoutes(newNNIPort *voltha.LogicalPort, copyFromNNIPort *voltha.LogicalPort) { |
| updatedRoutes := make(map[PathID][]Hop) |
| for key, val := range dr.Routes { |
| if key.Ingress == copyFromNNIPort.OfpPort.PortNo { |
| updatedRoutes[PathID{Ingress: newNNIPort.OfpPort.PortNo, Egress: key.Egress}] = []Hop{ |
| {DeviceID: newNNIPort.DeviceId, Ingress: newNNIPort.DevicePortNo, Egress: val[0].Egress}, |
| val[1], |
| } |
| } |
| if key.Egress == copyFromNNIPort.OfpPort.PortNo { |
| updatedRoutes[PathID{Ingress: key.Ingress, Egress: newNNIPort.OfpPort.PortNo}] = []Hop{ |
| val[0], |
| {DeviceID: newNNIPort.DeviceId, Ingress: val[1].Ingress, Egress: newNNIPort.DevicePortNo}, |
| } |
| } |
| updatedRoutes[key] = val |
| } |
| dr.Routes = updatedRoutes |
| } |
| |
| // reset cleans up the device graph |
| func (dr *DeviceRoutes) reset() { |
| dr.rootPortsLock.Lock() |
| dr.RootPorts = make(map[uint32]uint32) |
| dr.rootPortsLock.Unlock() |
| // Do not numGetDeviceCalledLock Routes, logicalPorts as the callee function already holds its numGetDeviceCalledLock. |
| dr.Routes = make(map[PathID][]Hop) |
| dr.logicalPorts = make([]*voltha.LogicalPort, 0) |
| dr.devicesPonPortsLock.Lock() |
| dr.devicesPonPorts = make(map[string][]*voltha.Port) |
| dr.devicesPonPortsLock.Unlock() |
| } |
| |
| //concatDeviceIdPortId formats a portid using the device id and the port number |
| func concatDeviceIDPortID(deviceID string, portNo uint32) string { |
| return fmt.Sprintf("%s:%d", deviceID, portNo) |
| } |
| |
| //getReverseRoute returns the reverse of the route |
| func getReverseRoute(route []Hop) []Hop { |
| reverse := make([]Hop, len(route)) |
| for i, j := 0, len(route)-1; j >= 0; i, j = i+1, j-1 { |
| reverse[i].DeviceID, reverse[i].Ingress, reverse[i].Egress = route[j].DeviceID, route[j].Egress, route[j].Ingress |
| } |
| return reverse |
| } |