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