blob: b80c20ca44eba085f87af2fb81b6ef2bf06434de [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 {
146 for _, rootDevicePeer := range rootDevicePort.Peers {
147 childDevice, err = dr.getDevice(ctx, rootDevicePeer.DeviceId)
148 if err != nil {
149 return err
150 }
151 childPonPorts := dr.getDevicePonPorts(childDevice.Id, nniPort.DeviceId)
152 if len(childPonPorts) < 1 {
153 err = status.Errorf(codes.Aborted, "no-child-pon-port-%s", childDevice.Id)
154 return err
155 }
156 // We use the first PON port on the ONU whose parent is the root device.
157 childPonPort := childPonPorts[0].PortNo
158 for _, childDevicePort := range childDevice.Ports {
159 if childDevicePort.Type == voltha.Port_ETHERNET_UNI {
160 childLogicalPort, exist := physPortToLogicalPortMap[concatDeviceIDPortID(childDevice.Id, childDevicePort.PortNo)]
161 if !exist {
162 // This can happen if this logical port has not been created yet for that device
163 continue
164 }
165 dr.Routes[PathID{Ingress: nniPort.OfpPort.PortNo, Egress: childLogicalPort}] = []Hop{
166 {DeviceID: rootDevice.Id, Ingress: nniPort.DevicePortNo, Egress: rootDevicePort.PortNo},
167 {DeviceID: childDevice.Id, Ingress: childPonPort, Egress: childDevicePort.PortNo},
168 }
169 dr.Routes[PathID{Ingress: childLogicalPort, Egress: nniPort.OfpPort.PortNo}] = getReverseRoute(
170 dr.Routes[PathID{Ingress: nniPort.OfpPort.PortNo, Egress: childLogicalPort}])
171 }
172 }
173 }
174 }
175 }
176 }
177 return nil
178}
179
180// verifyPrecondition verify whether the preconditions are met to proceed with addition of the new logical port
181func (dr *DeviceRoutes) addPortAndVerifyPrecondition(lp *voltha.LogicalPort) error {
182 var exist, nniLogicalPortExist, uniLogicalPortExist bool
183 for _, existingLogicalPort := range dr.logicalPorts {
184 nniLogicalPortExist = nniLogicalPortExist || existingLogicalPort.RootPort
185 uniLogicalPortExist = uniLogicalPortExist || !existingLogicalPort.RootPort
186 exist = exist || existingLogicalPort.OfpPort.PortNo == lp.OfpPort.PortNo
187 if nniLogicalPortExist && uniLogicalPortExist && exist {
188 break
189 }
190 }
191 if !exist {
192 dr.logicalPorts = append(dr.logicalPorts, lp)
193 nniLogicalPortExist = nniLogicalPortExist || lp.RootPort
194 uniLogicalPortExist = uniLogicalPortExist || !lp.RootPort
195 }
196
197 // If we do not have both NNI and UNI ports then return an error
198 if !(nniLogicalPortExist && uniLogicalPortExist) {
199 fmt.Println("errors", nniLogicalPortExist, uniLogicalPortExist)
200 return status.Error(codes.FailedPrecondition, "no-uni-and-nni-ports-combination")
201 }
202 return nil
203}
204
205// AddPort augments the current set of routes with new routes corresponding to the logical port "lp". If the routes have
206// not been built yet then use logical port "lps" to compute all current routes (lps includes lp)
207func (dr *DeviceRoutes) AddPort(ctx context.Context, lp *voltha.LogicalPort, lps []*voltha.LogicalPort) error {
208 log.Debugw("add-port-to-routes", log.Fields{"port": lp, "len-logical-ports": len(lps)})
209
210 dr.routeBuildLock.Lock()
211 if len(dr.Routes) == 0 {
212 dr.routeBuildLock.Unlock()
213 return dr.ComputeRoutes(ctx, lps)
214 }
215
216 // A set of routes exists
217 if err := dr.addPortAndVerifyPrecondition(lp); err != nil {
218 dr.reset()
219 dr.routeBuildLock.Unlock()
220 return err
221 }
222
223 defer dr.routeBuildLock.Unlock()
224 // Update the set of root ports, if applicable
225 if lp.RootPort {
226 dr.RootPorts[lp.OfpPort.PortNo] = lp.OfpPort.PortNo
227 }
228
229 var copyFromNNIPort *voltha.LogicalPort
230 // Setup the physical ports to logical ports map
231 nniPorts := make([]*voltha.LogicalPort, 0)
232 for _, lport := range dr.logicalPorts {
233 if lport.RootPort {
234 nniPorts = append(nniPorts, lport)
235 if copyFromNNIPort == nil && lport.OfpPort.PortNo != lp.OfpPort.PortNo {
236 copyFromNNIPort = lport
237 }
238 }
239 }
240
241 if copyFromNNIPort == nil {
242 // Trying to add the same NNI port. Just return
243 return nil
244 }
245
246 // Adding NNI Port? If we are here we already have an NNI port with a set of routes. Just copy the existing
247 // routes using an existing NNI port
248 if lp.RootPort {
249 dr.copyFromExistingNNIRoutes(lp, copyFromNNIPort)
250 return nil
251 }
252
253 // Adding a UNI port
254 for _, nniPort := range nniPorts {
255 childPonPorts := dr.getDevicePonPorts(lp.DeviceId, nniPort.DeviceId)
256 if len(childPonPorts) == 0 || len(childPonPorts[0].Peers) == 0 {
257 // Ports may not have been cached yet - get the device info which sets the PON port cache
258 if _, err := dr.getDevice(ctx, lp.DeviceId); err != nil {
259 dr.reset()
260 return err
261 }
262 childPonPorts = dr.getDevicePonPorts(lp.DeviceId, nniPort.DeviceId)
263 if len(childPonPorts) == 0 || len(childPonPorts[0].Peers) == 0 {
264 dr.reset()
265 return status.Errorf(codes.FailedPrecondition, "no-pon-ports-%s", lp.DeviceId)
266 }
267 }
268 // We use the first PON port on the child device
269 childPonPort := childPonPorts[0]
270 dr.Routes[PathID{Ingress: nniPort.OfpPort.PortNo, Egress: lp.OfpPort.PortNo}] = []Hop{
271 {DeviceID: nniPort.DeviceId, Ingress: nniPort.DevicePortNo, Egress: childPonPort.Peers[0].PortNo},
272 {DeviceID: lp.DeviceId, Ingress: childPonPort.PortNo, Egress: lp.DevicePortNo},
273 }
274 dr.Routes[PathID{Ingress: lp.OfpPort.PortNo, Egress: nniPort.OfpPort.PortNo}] = getReverseRoute(
275 dr.Routes[PathID{Ingress: nniPort.OfpPort.PortNo, Egress: lp.OfpPort.PortNo}])
276 }
277 return nil
278}
279
280// Print prints routes
281func (dr *DeviceRoutes) Print() error {
282 log.Debugw("Print", log.Fields{"logical-device-id": dr.logicalDeviceID, "logical-ports": dr.logicalPorts})
283 if log.V(log.DebugLevel) {
284 output := ""
285 routeNumber := 1
286 for k, v := range dr.Routes {
287 key := fmt.Sprintf("LP:%d->LP:%d", k.Ingress, k.Egress)
288 val := ""
289 for _, i := range v {
290 val += fmt.Sprintf("{%d->%s->%d},", i.Ingress, i.DeviceID, i.Egress)
291 }
292 val = val[:len(val)-1]
293 output += fmt.Sprintf("%d:{%s=>%s} ", routeNumber, key, fmt.Sprintf("[%s]", val))
294 routeNumber++
295 }
296 if len(dr.Routes) == 0 {
297 log.Debugw("no-routes-found", log.Fields{"logical-device-id": dr.logicalDeviceID})
298 } else {
299 log.Debugw("graph_routes", log.Fields{"lDeviceId": dr.logicalDeviceID, "Routes": output})
300 }
301 }
302 return nil
303}
304
305// IsUpToDate returns true if device is up to date
306func (dr *DeviceRoutes) IsUpToDate(ld *voltha.LogicalDevice) bool {
307 dr.routeBuildLock.Lock()
308 defer dr.routeBuildLock.Unlock()
309 numNNI, numUNI := 0, 0
310 if ld != nil {
311 if len(dr.logicalPorts) != len(ld.Ports) {
312 return false
313 }
314 numNNI = len(dr.RootPorts)
315 numUNI = len(ld.Ports) - numNNI
316 }
317 return len(dr.Routes) == numNNI*numUNI*2
318}
319
320// getDevicePonPorts returns all the PON ports of a device whose peer device ID is peerDeviceID
321func (dr *DeviceRoutes) getDevicePonPorts(deviceID string, peerDeviceID string) []*voltha.Port {
322 dr.devicesPonPortsLock.RLock()
323 defer dr.devicesPonPortsLock.RUnlock()
324 ponPorts := make([]*voltha.Port, 0)
325 ports, exist := dr.devicesPonPorts[deviceID]
326 if !exist {
327 return ponPorts
328 }
329 //fmt.Println("getDevicePonPorts", deviceID, peerDeviceID, ports)
330 for _, port := range ports {
331 for _, peer := range port.Peers {
332 if peer.DeviceId == peerDeviceID {
333 ponPorts = append(ponPorts, port)
334 }
335 }
336 }
337 return ponPorts
338}
339
340//getDevice returns the from the model and updates the PON ports map of that device.
341func (dr *DeviceRoutes) getDevice(ctx context.Context, deviceID string) (*voltha.Device, error) {
342 device, err := dr.getDeviceFromModel(ctx, deviceID)
343 if err != nil {
344 log.Errorw("device-not-found", log.Fields{"deviceId": deviceID, "error": err})
345 return nil, err
346 }
347 dr.devicesPonPortsLock.Lock()
348 defer dr.devicesPonPortsLock.Unlock()
349 for _, port := range device.Ports {
350 if port.Type == voltha.Port_PON_ONU || port.Type == voltha.Port_PON_OLT {
351 dr.devicesPonPorts[device.Id] = append(dr.devicesPonPorts[device.Id], port)
352 }
353 }
354 return device, nil
355}
356
357//copyFromExistingNNIRoutes copies routes from an existing set of NNI routes
358func (dr *DeviceRoutes) copyFromExistingNNIRoutes(newNNIPort *voltha.LogicalPort, copyFromNNIPort *voltha.LogicalPort) {
359 updatedRoutes := make(map[PathID][]Hop)
360 for key, val := range dr.Routes {
361 if key.Ingress == copyFromNNIPort.OfpPort.PortNo {
362 updatedRoutes[PathID{Ingress: newNNIPort.OfpPort.PortNo, Egress: key.Egress}] = []Hop{
363 {DeviceID: newNNIPort.DeviceId, Ingress: newNNIPort.DevicePortNo, Egress: val[0].Egress},
364 val[1],
365 }
366 }
367 if key.Egress == copyFromNNIPort.OfpPort.PortNo {
368 updatedRoutes[PathID{Ingress: key.Ingress, Egress: newNNIPort.OfpPort.PortNo}] = []Hop{
369 val[0],
370 {DeviceID: newNNIPort.DeviceId, Ingress: val[1].Ingress, Egress: newNNIPort.DevicePortNo},
371 }
372 }
373 updatedRoutes[key] = val
374 }
375 dr.Routes = updatedRoutes
376}
377
378// reset cleans up the device graph
379func (dr *DeviceRoutes) reset() {
380 dr.rootPortsLock.Lock()
381 dr.RootPorts = make(map[uint32]uint32)
382 dr.rootPortsLock.Unlock()
383 // Do not numGetDeviceCalledLock Routes, logicalPorts as the callee function already holds its numGetDeviceCalledLock.
384 dr.Routes = make(map[PathID][]Hop)
385 dr.logicalPorts = make([]*voltha.LogicalPort, 0)
386 dr.devicesPonPortsLock.Lock()
387 dr.devicesPonPorts = make(map[string][]*voltha.Port)
388 dr.devicesPonPortsLock.Unlock()
389}
390
391//concatDeviceIdPortId formats a portid using the device id and the port number
392func concatDeviceIDPortID(deviceID string, portNo uint32) string {
393 return fmt.Sprintf("%s:%d", deviceID, portNo)
394}
395
396//getReverseRoute returns the reverse of the route
397func getReverseRoute(route []Hop) []Hop {
398 reverse := make([]Hop, len(route))
399 for i, j := 0, len(route)-1; j >= 0; i, j = i+1, j-1 {
400 reverse[i].DeviceID, reverse[i].Ingress, reverse[i].Egress = route[j].DeviceID, route[j].Egress, route[j].Ingress
401 }
402 return reverse
403}