blob: c22ff2acffc256753c7f209dfbf66560ad72ec46 [file] [log] [blame]
khenaidoo820197c2020-02-13 16:35:33 -05001/*
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
17package route
18
19import (
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
29func 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
37type 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
44type PathID struct {
45 Ingress uint32
46 Egress uint32
47}
48
49type OFPortLink struct {
50 Ingress uint32
51 Egress uint32
52}
53
54// GetDeviceFunc returns device function
55type 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
58type 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
71func 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
83func (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
91func (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 {
khenaidoo80b987d2020-02-20 10:52:52 -0500146 log.Debugw("peers", log.Fields{"root-device-id": rootDevice.Id, "port-no": rootDevicePort.PortNo, "len-peers": len(rootDevicePort.Peers)})
khenaidoo820197c2020-02-13 16:35:33 -0500147 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
182func (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)
208func (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
282func (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
307func (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
322func (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.
342func (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
359func (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
380func (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
393func concatDeviceIDPortID(deviceID string, portNo uint32) string {
394 return fmt.Sprintf("%s:%d", deviceID, portNo)
395}
396
397//getReverseRoute returns the reverse of the route
398func 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}