khenaidoo | 820197c | 2020-02-13 16:35:33 -0500 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2020-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 | */ |
| 16 | |
| 17 | package route |
| 18 | |
| 19 | import ( |
| 20 | "context" |
| 21 | "fmt" |
| 22 | "github.com/opencord/voltha-lib-go/v3/pkg/log" |
| 23 | "github.com/opencord/voltha-protos/v3/go/voltha" |
| 24 | "google.golang.org/grpc/codes" |
| 25 | "google.golang.org/grpc/status" |
| 26 | "sync" |
| 27 | ) |
| 28 | |
khenaidoo | 820197c | 2020-02-13 16:35:33 -0500 | [diff] [blame] | 29 | // Hop represent a route hop |
| 30 | type Hop struct { |
| 31 | DeviceID string |
| 32 | Ingress uint32 |
| 33 | Egress uint32 |
| 34 | } |
| 35 | |
| 36 | // PathID is the identification of a route between two logical ports |
| 37 | type PathID struct { |
| 38 | Ingress uint32 |
| 39 | Egress uint32 |
| 40 | } |
| 41 | |
| 42 | type OFPortLink struct { |
| 43 | Ingress uint32 |
| 44 | Egress uint32 |
| 45 | } |
| 46 | |
| 47 | // GetDeviceFunc returns device function |
| 48 | type GetDeviceFunc func(ctx context.Context, id string) (*voltha.Device, error) |
| 49 | |
| 50 | // DeviceRoutes represent the set of routes between logical ports of a logical device |
| 51 | type DeviceRoutes struct { |
| 52 | logicalDeviceID string |
| 53 | getDeviceFromModel GetDeviceFunc |
| 54 | logicalPorts []*voltha.LogicalPort |
| 55 | RootPorts map[uint32]uint32 |
| 56 | rootPortsLock sync.RWMutex |
| 57 | Routes map[PathID][]Hop |
| 58 | routeBuildLock sync.RWMutex |
| 59 | devicesPonPorts map[string][]*voltha.Port |
| 60 | devicesPonPortsLock sync.RWMutex |
| 61 | } |
| 62 | |
| 63 | // NewDeviceRoutes creates device graph instance |
| 64 | func NewDeviceRoutes(logicalDeviceID string, getDevice GetDeviceFunc) *DeviceRoutes { |
| 65 | var dr DeviceRoutes |
| 66 | dr.logicalDeviceID = logicalDeviceID |
| 67 | dr.getDeviceFromModel = getDevice |
| 68 | dr.RootPorts = make(map[uint32]uint32) |
| 69 | dr.Routes = make(map[PathID][]Hop) |
| 70 | dr.devicesPonPorts = make(map[string][]*voltha.Port) |
Girish Kumar | f56a468 | 2020-03-20 20:07:46 +0000 | [diff] [blame] | 71 | logger.Debug("new device routes created ...") |
khenaidoo | 820197c | 2020-02-13 16:35:33 -0500 | [diff] [blame] | 72 | return &dr |
| 73 | } |
| 74 | |
| 75 | //IsRootPort returns true if the port is a root port on a logical device |
| 76 | func (dr *DeviceRoutes) IsRootPort(port uint32) bool { |
| 77 | dr.rootPortsLock.RLock() |
| 78 | defer dr.rootPortsLock.RUnlock() |
| 79 | _, exist := dr.RootPorts[port] |
| 80 | return exist |
| 81 | } |
| 82 | |
| 83 | //ComputeRoutes calculates all the routes between the logical ports. This will clear up any existing route |
| 84 | func (dr *DeviceRoutes) ComputeRoutes(ctx context.Context, lps []*voltha.LogicalPort) error { |
| 85 | dr.routeBuildLock.Lock() |
| 86 | defer dr.routeBuildLock.Unlock() |
| 87 | |
Girish Kumar | f56a468 | 2020-03-20 20:07:46 +0000 | [diff] [blame] | 88 | logger.Debugw("computing-all-routes", log.Fields{"len-logical-ports": len(lps)}) |
khenaidoo | 820197c | 2020-02-13 16:35:33 -0500 | [diff] [blame] | 89 | var err error |
| 90 | defer func() { |
| 91 | // On error, clear the routes - any flow request or a port add/delete will trigger the rebuild |
| 92 | if err != nil { |
| 93 | dr.reset() |
| 94 | } |
| 95 | }() |
| 96 | |
| 97 | if len(lps) < 2 { |
| 98 | return status.Error(codes.FailedPrecondition, "not-enough-logical-ports") |
| 99 | } |
| 100 | |
| 101 | dr.reset() |
| 102 | dr.logicalPorts = append(dr.logicalPorts, lps...) |
| 103 | |
| 104 | // Setup the physical ports to logical ports map, the nni ports as well as the root ports map |
| 105 | physPortToLogicalPortMap := make(map[string]uint32) |
| 106 | nniPorts := make([]*voltha.LogicalPort, 0) |
| 107 | for _, lp := range lps { |
| 108 | physPortToLogicalPortMap[concatDeviceIDPortID(lp.DeviceId, lp.DevicePortNo)] = lp.OfpPort.PortNo |
| 109 | if lp.RootPort { |
| 110 | nniPorts = append(nniPorts, lp) |
| 111 | dr.RootPorts[lp.OfpPort.PortNo] = lp.OfpPort.PortNo |
| 112 | } |
| 113 | } |
| 114 | if len(nniPorts) == 0 { |
| 115 | err = status.Error(codes.FailedPrecondition, "no nni port") |
| 116 | return err |
| 117 | } |
| 118 | var rootDevice *voltha.Device |
| 119 | var childDevice *voltha.Device |
| 120 | var copyFromNNIPort *voltha.LogicalPort |
| 121 | for idx, nniPort := range nniPorts { |
| 122 | if idx == 0 { |
| 123 | copyFromNNIPort = nniPort |
| 124 | } else if len(dr.Routes) > 0 { |
| 125 | dr.copyFromExistingNNIRoutes(nniPort, copyFromNNIPort) |
| 126 | return nil |
| 127 | } |
| 128 | // Get root device |
| 129 | rootDevice, err = dr.getDevice(ctx, nniPort.DeviceId) |
| 130 | if err != nil { |
| 131 | return err |
| 132 | } |
| 133 | if len(rootDevice.Ports) == 0 { |
| 134 | err = status.Errorf(codes.FailedPrecondition, "no-port-%s", rootDevice.Id) |
| 135 | return err |
| 136 | } |
| 137 | for _, rootDevicePort := range rootDevice.Ports { |
| 138 | if rootDevicePort.Type == voltha.Port_PON_OLT { |
Girish Kumar | f56a468 | 2020-03-20 20:07:46 +0000 | [diff] [blame] | 139 | logger.Debugw("peers", log.Fields{"root-device-id": rootDevice.Id, "port-no": rootDevicePort.PortNo, "len-peers": len(rootDevicePort.Peers)}) |
khenaidoo | 820197c | 2020-02-13 16:35:33 -0500 | [diff] [blame] | 140 | for _, rootDevicePeer := range rootDevicePort.Peers { |
| 141 | childDevice, err = dr.getDevice(ctx, rootDevicePeer.DeviceId) |
| 142 | if err != nil { |
| 143 | return err |
| 144 | } |
| 145 | childPonPorts := dr.getDevicePonPorts(childDevice.Id, nniPort.DeviceId) |
| 146 | if len(childPonPorts) < 1 { |
| 147 | err = status.Errorf(codes.Aborted, "no-child-pon-port-%s", childDevice.Id) |
| 148 | return err |
| 149 | } |
| 150 | // We use the first PON port on the ONU whose parent is the root device. |
| 151 | childPonPort := childPonPorts[0].PortNo |
| 152 | for _, childDevicePort := range childDevice.Ports { |
| 153 | if childDevicePort.Type == voltha.Port_ETHERNET_UNI { |
| 154 | childLogicalPort, exist := physPortToLogicalPortMap[concatDeviceIDPortID(childDevice.Id, childDevicePort.PortNo)] |
| 155 | if !exist { |
| 156 | // This can happen if this logical port has not been created yet for that device |
| 157 | continue |
| 158 | } |
| 159 | dr.Routes[PathID{Ingress: nniPort.OfpPort.PortNo, Egress: childLogicalPort}] = []Hop{ |
| 160 | {DeviceID: rootDevice.Id, Ingress: nniPort.DevicePortNo, Egress: rootDevicePort.PortNo}, |
| 161 | {DeviceID: childDevice.Id, Ingress: childPonPort, Egress: childDevicePort.PortNo}, |
| 162 | } |
| 163 | dr.Routes[PathID{Ingress: childLogicalPort, Egress: nniPort.OfpPort.PortNo}] = getReverseRoute( |
| 164 | dr.Routes[PathID{Ingress: nniPort.OfpPort.PortNo, Egress: childLogicalPort}]) |
| 165 | } |
| 166 | } |
| 167 | } |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | return nil |
| 172 | } |
| 173 | |
| 174 | // verifyPrecondition verify whether the preconditions are met to proceed with addition of the new logical port |
| 175 | func (dr *DeviceRoutes) addPortAndVerifyPrecondition(lp *voltha.LogicalPort) error { |
| 176 | var exist, nniLogicalPortExist, uniLogicalPortExist bool |
| 177 | for _, existingLogicalPort := range dr.logicalPorts { |
| 178 | nniLogicalPortExist = nniLogicalPortExist || existingLogicalPort.RootPort |
| 179 | uniLogicalPortExist = uniLogicalPortExist || !existingLogicalPort.RootPort |
| 180 | exist = exist || existingLogicalPort.OfpPort.PortNo == lp.OfpPort.PortNo |
| 181 | if nniLogicalPortExist && uniLogicalPortExist && exist { |
| 182 | break |
| 183 | } |
| 184 | } |
| 185 | if !exist { |
| 186 | dr.logicalPorts = append(dr.logicalPorts, lp) |
| 187 | nniLogicalPortExist = nniLogicalPortExist || lp.RootPort |
| 188 | uniLogicalPortExist = uniLogicalPortExist || !lp.RootPort |
| 189 | } |
| 190 | |
| 191 | // If we do not have both NNI and UNI ports then return an error |
| 192 | if !(nniLogicalPortExist && uniLogicalPortExist) { |
| 193 | fmt.Println("errors", nniLogicalPortExist, uniLogicalPortExist) |
| 194 | return status.Error(codes.FailedPrecondition, "no-uni-and-nni-ports-combination") |
| 195 | } |
| 196 | return nil |
| 197 | } |
| 198 | |
| 199 | // AddPort augments the current set of routes with new routes corresponding to the logical port "lp". If the routes have |
| 200 | // not been built yet then use logical port "lps" to compute all current routes (lps includes lp) |
| 201 | func (dr *DeviceRoutes) AddPort(ctx context.Context, lp *voltha.LogicalPort, lps []*voltha.LogicalPort) error { |
Girish Kumar | f56a468 | 2020-03-20 20:07:46 +0000 | [diff] [blame] | 202 | logger.Debugw("add-port-to-routes", log.Fields{"port": lp, "len-logical-ports": len(lps)}) |
khenaidoo | 820197c | 2020-02-13 16:35:33 -0500 | [diff] [blame] | 203 | |
| 204 | dr.routeBuildLock.Lock() |
| 205 | if len(dr.Routes) == 0 { |
| 206 | dr.routeBuildLock.Unlock() |
| 207 | return dr.ComputeRoutes(ctx, lps) |
| 208 | } |
| 209 | |
| 210 | // A set of routes exists |
| 211 | if err := dr.addPortAndVerifyPrecondition(lp); err != nil { |
| 212 | dr.reset() |
| 213 | dr.routeBuildLock.Unlock() |
| 214 | return err |
| 215 | } |
| 216 | |
| 217 | defer dr.routeBuildLock.Unlock() |
| 218 | // Update the set of root ports, if applicable |
| 219 | if lp.RootPort { |
| 220 | dr.RootPorts[lp.OfpPort.PortNo] = lp.OfpPort.PortNo |
| 221 | } |
| 222 | |
| 223 | var copyFromNNIPort *voltha.LogicalPort |
| 224 | // Setup the physical ports to logical ports map |
| 225 | nniPorts := make([]*voltha.LogicalPort, 0) |
| 226 | for _, lport := range dr.logicalPorts { |
| 227 | if lport.RootPort { |
| 228 | nniPorts = append(nniPorts, lport) |
| 229 | if copyFromNNIPort == nil && lport.OfpPort.PortNo != lp.OfpPort.PortNo { |
| 230 | copyFromNNIPort = lport |
| 231 | } |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | if copyFromNNIPort == nil { |
| 236 | // Trying to add the same NNI port. Just return |
| 237 | return nil |
| 238 | } |
| 239 | |
| 240 | // Adding NNI Port? If we are here we already have an NNI port with a set of routes. Just copy the existing |
| 241 | // routes using an existing NNI port |
| 242 | if lp.RootPort { |
| 243 | dr.copyFromExistingNNIRoutes(lp, copyFromNNIPort) |
| 244 | return nil |
| 245 | } |
| 246 | |
| 247 | // Adding a UNI port |
| 248 | for _, nniPort := range nniPorts { |
| 249 | childPonPorts := dr.getDevicePonPorts(lp.DeviceId, nniPort.DeviceId) |
| 250 | if len(childPonPorts) == 0 || len(childPonPorts[0].Peers) == 0 { |
| 251 | // Ports may not have been cached yet - get the device info which sets the PON port cache |
| 252 | if _, err := dr.getDevice(ctx, lp.DeviceId); err != nil { |
| 253 | dr.reset() |
| 254 | return err |
| 255 | } |
| 256 | childPonPorts = dr.getDevicePonPorts(lp.DeviceId, nniPort.DeviceId) |
| 257 | if len(childPonPorts) == 0 || len(childPonPorts[0].Peers) == 0 { |
| 258 | dr.reset() |
| 259 | return status.Errorf(codes.FailedPrecondition, "no-pon-ports-%s", lp.DeviceId) |
| 260 | } |
| 261 | } |
| 262 | // We use the first PON port on the child device |
| 263 | childPonPort := childPonPorts[0] |
| 264 | dr.Routes[PathID{Ingress: nniPort.OfpPort.PortNo, Egress: lp.OfpPort.PortNo}] = []Hop{ |
| 265 | {DeviceID: nniPort.DeviceId, Ingress: nniPort.DevicePortNo, Egress: childPonPort.Peers[0].PortNo}, |
| 266 | {DeviceID: lp.DeviceId, Ingress: childPonPort.PortNo, Egress: lp.DevicePortNo}, |
| 267 | } |
| 268 | dr.Routes[PathID{Ingress: lp.OfpPort.PortNo, Egress: nniPort.OfpPort.PortNo}] = getReverseRoute( |
| 269 | dr.Routes[PathID{Ingress: nniPort.OfpPort.PortNo, Egress: lp.OfpPort.PortNo}]) |
| 270 | } |
| 271 | return nil |
| 272 | } |
| 273 | |
| 274 | // Print prints routes |
| 275 | func (dr *DeviceRoutes) Print() error { |
Girish Kumar | f56a468 | 2020-03-20 20:07:46 +0000 | [diff] [blame] | 276 | logger.Debugw("Print", log.Fields{"logical-device-id": dr.logicalDeviceID, "logical-ports": dr.logicalPorts}) |
| 277 | if logger.V(log.DebugLevel) { |
khenaidoo | 820197c | 2020-02-13 16:35:33 -0500 | [diff] [blame] | 278 | output := "" |
| 279 | routeNumber := 1 |
| 280 | for k, v := range dr.Routes { |
| 281 | key := fmt.Sprintf("LP:%d->LP:%d", k.Ingress, k.Egress) |
| 282 | val := "" |
| 283 | for _, i := range v { |
| 284 | val += fmt.Sprintf("{%d->%s->%d},", i.Ingress, i.DeviceID, i.Egress) |
| 285 | } |
| 286 | val = val[:len(val)-1] |
| 287 | output += fmt.Sprintf("%d:{%s=>%s} ", routeNumber, key, fmt.Sprintf("[%s]", val)) |
| 288 | routeNumber++ |
| 289 | } |
| 290 | if len(dr.Routes) == 0 { |
Girish Kumar | f56a468 | 2020-03-20 20:07:46 +0000 | [diff] [blame] | 291 | logger.Debugw("no-routes-found", log.Fields{"logical-device-id": dr.logicalDeviceID}) |
khenaidoo | 820197c | 2020-02-13 16:35:33 -0500 | [diff] [blame] | 292 | } else { |
Girish Kumar | f56a468 | 2020-03-20 20:07:46 +0000 | [diff] [blame] | 293 | logger.Debugw("graph_routes", log.Fields{"lDeviceId": dr.logicalDeviceID, "Routes": output}) |
khenaidoo | 820197c | 2020-02-13 16:35:33 -0500 | [diff] [blame] | 294 | } |
| 295 | } |
| 296 | return nil |
| 297 | } |
| 298 | |
| 299 | // IsUpToDate returns true if device is up to date |
| 300 | func (dr *DeviceRoutes) IsUpToDate(ld *voltha.LogicalDevice) bool { |
| 301 | dr.routeBuildLock.Lock() |
| 302 | defer dr.routeBuildLock.Unlock() |
| 303 | numNNI, numUNI := 0, 0 |
| 304 | if ld != nil { |
| 305 | if len(dr.logicalPorts) != len(ld.Ports) { |
| 306 | return false |
| 307 | } |
| 308 | numNNI = len(dr.RootPorts) |
| 309 | numUNI = len(ld.Ports) - numNNI |
| 310 | } |
| 311 | return len(dr.Routes) == numNNI*numUNI*2 |
| 312 | } |
| 313 | |
| 314 | // getDevicePonPorts returns all the PON ports of a device whose peer device ID is peerDeviceID |
| 315 | func (dr *DeviceRoutes) getDevicePonPorts(deviceID string, peerDeviceID string) []*voltha.Port { |
| 316 | dr.devicesPonPortsLock.RLock() |
| 317 | defer dr.devicesPonPortsLock.RUnlock() |
| 318 | ponPorts := make([]*voltha.Port, 0) |
| 319 | ports, exist := dr.devicesPonPorts[deviceID] |
| 320 | if !exist { |
| 321 | return ponPorts |
| 322 | } |
| 323 | //fmt.Println("getDevicePonPorts", deviceID, peerDeviceID, ports) |
| 324 | for _, port := range ports { |
| 325 | for _, peer := range port.Peers { |
| 326 | if peer.DeviceId == peerDeviceID { |
| 327 | ponPorts = append(ponPorts, port) |
| 328 | } |
| 329 | } |
| 330 | } |
| 331 | return ponPorts |
| 332 | } |
| 333 | |
| 334 | //getDevice returns the from the model and updates the PON ports map of that device. |
| 335 | func (dr *DeviceRoutes) getDevice(ctx context.Context, deviceID string) (*voltha.Device, error) { |
| 336 | device, err := dr.getDeviceFromModel(ctx, deviceID) |
| 337 | if err != nil { |
Girish Kumar | f56a468 | 2020-03-20 20:07:46 +0000 | [diff] [blame] | 338 | logger.Errorw("device-not-found", log.Fields{"deviceId": deviceID, "error": err}) |
khenaidoo | 820197c | 2020-02-13 16:35:33 -0500 | [diff] [blame] | 339 | return nil, err |
| 340 | } |
| 341 | dr.devicesPonPortsLock.Lock() |
| 342 | defer dr.devicesPonPortsLock.Unlock() |
| 343 | for _, port := range device.Ports { |
| 344 | if port.Type == voltha.Port_PON_ONU || port.Type == voltha.Port_PON_OLT { |
| 345 | dr.devicesPonPorts[device.Id] = append(dr.devicesPonPorts[device.Id], port) |
| 346 | } |
| 347 | } |
| 348 | return device, nil |
| 349 | } |
| 350 | |
| 351 | //copyFromExistingNNIRoutes copies routes from an existing set of NNI routes |
| 352 | func (dr *DeviceRoutes) copyFromExistingNNIRoutes(newNNIPort *voltha.LogicalPort, copyFromNNIPort *voltha.LogicalPort) { |
| 353 | updatedRoutes := make(map[PathID][]Hop) |
| 354 | for key, val := range dr.Routes { |
| 355 | if key.Ingress == copyFromNNIPort.OfpPort.PortNo { |
| 356 | updatedRoutes[PathID{Ingress: newNNIPort.OfpPort.PortNo, Egress: key.Egress}] = []Hop{ |
| 357 | {DeviceID: newNNIPort.DeviceId, Ingress: newNNIPort.DevicePortNo, Egress: val[0].Egress}, |
| 358 | val[1], |
| 359 | } |
| 360 | } |
| 361 | if key.Egress == copyFromNNIPort.OfpPort.PortNo { |
| 362 | updatedRoutes[PathID{Ingress: key.Ingress, Egress: newNNIPort.OfpPort.PortNo}] = []Hop{ |
| 363 | val[0], |
| 364 | {DeviceID: newNNIPort.DeviceId, Ingress: val[1].Ingress, Egress: newNNIPort.DevicePortNo}, |
| 365 | } |
| 366 | } |
| 367 | updatedRoutes[key] = val |
| 368 | } |
| 369 | dr.Routes = updatedRoutes |
| 370 | } |
| 371 | |
| 372 | // reset cleans up the device graph |
| 373 | func (dr *DeviceRoutes) reset() { |
| 374 | dr.rootPortsLock.Lock() |
| 375 | dr.RootPorts = make(map[uint32]uint32) |
| 376 | dr.rootPortsLock.Unlock() |
| 377 | // Do not numGetDeviceCalledLock Routes, logicalPorts as the callee function already holds its numGetDeviceCalledLock. |
| 378 | dr.Routes = make(map[PathID][]Hop) |
| 379 | dr.logicalPorts = make([]*voltha.LogicalPort, 0) |
| 380 | dr.devicesPonPortsLock.Lock() |
| 381 | dr.devicesPonPorts = make(map[string][]*voltha.Port) |
| 382 | dr.devicesPonPortsLock.Unlock() |
| 383 | } |
| 384 | |
| 385 | //concatDeviceIdPortId formats a portid using the device id and the port number |
| 386 | func concatDeviceIDPortID(deviceID string, portNo uint32) string { |
| 387 | return fmt.Sprintf("%s:%d", deviceID, portNo) |
| 388 | } |
| 389 | |
| 390 | //getReverseRoute returns the reverse of the route |
| 391 | func getReverseRoute(route []Hop) []Hop { |
| 392 | reverse := make([]Hop, len(route)) |
| 393 | for i, j := 0, len(route)-1; j >= 0; i, j = i+1, j-1 { |
| 394 | reverse[i].DeviceID, reverse[i].Ingress, reverse[i].Egress = route[j].DeviceID, route[j].Egress, route[j].Ingress |
| 395 | } |
| 396 | return reverse |
| 397 | } |