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