blob: a36c8415fa0be6050399e89ffec518704c5fa56b [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
khenaidoo820197c2020-02-13 16:35:33 -050029// Hop represent a route hop
30type 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
37type PathID struct {
38 Ingress uint32
39 Egress uint32
40}
41
42type OFPortLink struct {
43 Ingress uint32
44 Egress uint32
45}
46
47// GetDeviceFunc returns device function
48type 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
51type 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
64func 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 Kumarf56a4682020-03-20 20:07:46 +000071 logger.Debug("new device routes created ...")
khenaidoo820197c2020-02-13 16:35:33 -050072 return &dr
73}
74
75//IsRootPort returns true if the port is a root port on a logical device
76func (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
84func (dr *DeviceRoutes) ComputeRoutes(ctx context.Context, lps []*voltha.LogicalPort) error {
85 dr.routeBuildLock.Lock()
86 defer dr.routeBuildLock.Unlock()
87
Girish Kumarf56a4682020-03-20 20:07:46 +000088 logger.Debugw("computing-all-routes", log.Fields{"len-logical-ports": len(lps)})
khenaidoo820197c2020-02-13 16:35:33 -050089 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 Kumarf56a4682020-03-20 20:07:46 +0000139 logger.Debugw("peers", log.Fields{"root-device-id": rootDevice.Id, "port-no": rootDevicePort.PortNo, "len-peers": len(rootDevicePort.Peers)})
khenaidoo820197c2020-02-13 16:35:33 -0500140 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
175func (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)
201func (dr *DeviceRoutes) AddPort(ctx context.Context, lp *voltha.LogicalPort, lps []*voltha.LogicalPort) error {
Girish Kumarf56a4682020-03-20 20:07:46 +0000202 logger.Debugw("add-port-to-routes", log.Fields{"port": lp, "len-logical-ports": len(lps)})
khenaidoo820197c2020-02-13 16:35:33 -0500203
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
275func (dr *DeviceRoutes) Print() error {
Girish Kumarf56a4682020-03-20 20:07:46 +0000276 logger.Debugw("Print", log.Fields{"logical-device-id": dr.logicalDeviceID, "logical-ports": dr.logicalPorts})
277 if logger.V(log.DebugLevel) {
khenaidoo820197c2020-02-13 16:35:33 -0500278 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 Kumarf56a4682020-03-20 20:07:46 +0000291 logger.Debugw("no-routes-found", log.Fields{"logical-device-id": dr.logicalDeviceID})
khenaidoo820197c2020-02-13 16:35:33 -0500292 } else {
Girish Kumarf56a4682020-03-20 20:07:46 +0000293 logger.Debugw("graph_routes", log.Fields{"lDeviceId": dr.logicalDeviceID, "Routes": output})
khenaidoo820197c2020-02-13 16:35:33 -0500294 }
295 }
296 return nil
297}
298
299// IsUpToDate returns true if device is up to date
300func (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
315func (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.
335func (dr *DeviceRoutes) getDevice(ctx context.Context, deviceID string) (*voltha.Device, error) {
336 device, err := dr.getDeviceFromModel(ctx, deviceID)
337 if err != nil {
Girish Kumarf56a4682020-03-20 20:07:46 +0000338 logger.Errorw("device-not-found", log.Fields{"deviceId": deviceID, "error": err})
khenaidoo820197c2020-02-13 16:35:33 -0500339 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
352func (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
373func (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
386func concatDeviceIDPortID(deviceID string, portNo uint32) string {
387 return fmt.Sprintf("%s:%d", deviceID, portNo)
388}
389
390//getReverseRoute returns the reverse of the route
391func 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}