Path Computation Algorithms
[bgpcep.git] / algo / algo-impl / src / main / java / org / opendaylight / algo / impl / Samcra.java
1 /*
2  * Copyright (c) 2020 Orange. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.algo.impl;
9
10 import java.util.ArrayList;
11 import java.util.HashMap;
12 import java.util.List;
13
14 import org.opendaylight.graph.ConnectedEdge;
15 import org.opendaylight.graph.ConnectedGraph;
16 import org.opendaylight.graph.ConnectedVertex;
17 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.graph.rev191125.Delay;
18 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.graph.rev191125.graph.topology.graph.VertexKey;
19 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.path.computation.rev200120.ComputationStatus;
20 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.path.computation.rev200120.ConstrainedPath;
21 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.path.computation.rev200120.ConstrainedPathBuilder;
22 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.path.computation.rev200120.PathConstraints;
23 import org.opendaylight.yangtools.yang.common.Uint32;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 /**
28  * This Class implements the Self Adaptive Multiple Constraints Routing Algorithm (SAMCRA) a Path Computation Algorithm.
29  * The SAMCRA algorithm take into account the Bandwidth, TE Metric and Delay as composite constraints.
30  * Details of SAMCRA algorithm could be found in the article "Concepts of Exact QoS Routing Algorithms",
31  * Piet Van Mieghem and Fernando A. Kuipers, IEEE/ACM Transactions on Networking, Volume 12, Number 5, October 2004.
32  *
33  * @author Philippe Niger
34  * @author Olivier Dugeon
35  * @author Philippe Cadro
36  *
37  */
38
39 public class Samcra extends AbstractPathComputation {
40
41     /*
42      * This class stores the set of paths which has been computed for a given Connected Vertex:
43      *     - pathCount        number of active paths
44      *     - pathCurrent      node path currently in the priority queue (path with minimal length)
45      *     - pathList         list of computed paths
46      *
47      * Each path is represented by a "CspfPath" class to encompass path predecessor, path status
48      * and path length information
49      */
50
51     private static class SamcraPath {
52
53         private ConnectedVertex cvertex;
54         private int pathCount;
55         private CspfPath currentPath = null;
56         private ArrayList<CspfPath> pathList;
57
58         SamcraPath(ConnectedVertex vertex) {
59             this.cvertex = vertex;
60             this.pathCount = 0;
61             pathList = new ArrayList<CspfPath>();
62         }
63
64         public ConnectedVertex getVertex() {
65             return this.cvertex;
66         }
67
68         public void decrementPathCount() {
69             this.pathCount--;
70         }
71
72         public void incrementPathCount() {
73             this.pathCount++;
74         }
75
76         public int getPathCount() {
77             return this.pathCount;
78         }
79
80         public void setCurrentPath(CspfPath path) {
81             this.currentPath = path;
82         }
83
84         public CspfPath getCurrentPath() {
85             return this.currentPath;
86         }
87
88         public void addPath(CspfPath path) {
89             this.pathList.add(path);
90         }
91
92         public ArrayList<CspfPath> getPathList() {
93             return this.pathList;
94         }
95     }
96
97     private static final Logger LOG = LoggerFactory.getLogger(Samcra.class);
98
99     /* List of potential Samcra Path that satisfy given constraints */
100     private HashMap<Long, SamcraPath> samcraPaths;
101
102     /* TE Metric cost and Delay cost for the current selected Path */
103     int teCost = Integer.MAX_VALUE;
104     int delayCost = Integer.MAX_VALUE;
105
106     public Samcra(ConnectedGraph graph) {
107         super(graph);
108         samcraPaths = new HashMap<Long, SamcraPath>();
109     }
110
111     /* Samcra Algo:
112      *
113      * To limit the modification outside the Samcra method the same set of parameters as
114      * the CSPF method is used (related to pseudo code, the path length is computed inside
115      * the method based on the individual constraint parameters).
116      *
117      * On contrast to a simple CSPF algo, with Samcra a connected vertex might be associated to several
118      * metric vectors from which different path lengths are computed. However a connected vertex is only
119      * present once in the priority queue, associated to the minimal path weight, which is used as key
120      * to address the priority queue.
121      *
122      * For a given metric the path weight is an integer value computed as the entire part of
123      * the quantity:
124      *      100 * (vector_path_metric/target_metric)
125      * The path weight correspond to the maximum length computed from either the delay or TE metric.
126      *
127      * To maintain the priority queue behavior unchanged, a "SamcraPath" classes is created to manage
128      * the set of possible paths associated to a given vertex (see above).
129      *
130      */
131
132     @Override
133     public ConstrainedPath computeP2pPath(VertexKey src, VertexKey dst, PathConstraints cts) {
134         ConstrainedPathBuilder cpathBuilder;
135         List<ConnectedEdge> edges;
136         CspfPath currentPath;
137
138         LOG.info("Start SAMCRA Path Computation from {} to {} with constraints {}", src, dst, cts);
139
140         /* Initialize SAMCRA variables */
141         this.constraints = cts;
142         cpathBuilder = initializePathComputation(src, dst);
143         if (cpathBuilder.getStatus() == ComputationStatus.Failed) {
144             return cpathBuilder.build();
145         }
146         cpathBuilder.setBandwidth(cts.getBandwidth()).setClassType(cts.getClassType());
147
148         samcraPaths.clear();
149         samcraPaths.put(pathSource.getVertexKey(), new SamcraPath(pathSource.getVertex()));
150         samcraPaths.put(pathDestination.getVertexKey(), new SamcraPath(pathDestination.getVertex()));
151
152         /* Exploration of the priority queue:
153          * Each connected vertex is represented only once in the priority queue associated to the path
154          * with the minimal length (other path are stored in the SamcraPath object).
155          * The top of the queue, i.e. the element with the minimal key( path weight), is processed at each loop
156          */
157         while (priorityQueue.size() != 0) {
158             currentPath = priorityQueue.poll();
159             LOG.debug("Process path to Vertex {} from Priority Queue", currentPath.getVertex().toString());
160
161             /* Prepare Samcra Path from current CSP Path except for the source */
162             if (!(currentPath.equals(pathSource))) {
163                 SamcraPath currentSamcraPath = samcraPaths.get(currentPath.getVertexKey());
164                 CspfPath currentCspfPath = currentSamcraPath.getCurrentPath();
165                 float queuePathLength = currentCspfPath.getPathLength();
166                 LOG.trace("Samcra: priority Queue output SamcraPaths: {} CurrentPath: {} PathLength: {}",
167                         currentSamcraPath.currentPath.getPath(), currentCspfPath.getPath(), queuePathLength);
168             }
169
170             edges = currentPath.getVertex().getOutputConnectedEdges();
171             float currentPathLength = 1.0F;
172             for (ConnectedEdge edge : edges) {
173                 /* Connected Vertex's edges processing:
174                  * Prune the connected edges that do not satisfy the constraints (Bandwidth, TE Metric, Delay, Loss)
175                  * For each remaining edge process the path to the remote vertex using the "relaxSamcra" procedure
176                  *
177                  * If the return path length is positive, the destination is reached and the
178                  * obtained route satisfies the requested constraints.
179                  * The path length is checked to record only the optimal route (i.e. the route with
180                  * the minimal path length) info obtained from the destination vertex
181                  */
182                 if (pruneEdge(edge, currentPath)) {
183                     LOG.trace("  Prune Edge {}", edge.toString());
184                     continue;
185                 }
186                 float pathLength = relaxSamcra(edge, currentPath, pathSource);
187
188                 /* Check if we found a valid and better path */
189                 if ((pathLength > 0F) && (pathLength <= currentPathLength)) {
190                     final SamcraPath finalPath = samcraPaths.get(pathDestination.getVertexKey());
191                     cpathBuilder.setPathDescription(getPathDescription(finalPath.getCurrentPath().getPath()))
192                             .setMetric(Uint32.valueOf(pathDestination.getCost()))
193                             .setDelay(new Delay(Uint32.valueOf(pathDestination.getDelay())))
194                             .setStatus(ComputationStatus.Active);
195                     LOG.debug("Samcra: path to destination found and registered: {}",
196                             cpathBuilder.getPathDescription());
197                     currentPathLength = pathLength;
198                 }
199             }
200
201             /* The connected vertex that has been removed from the priority queue may have to be re-inserted with
202              * the minimal length non-dominated path associated to the connected vertex if it exists (to be done
203              * except for the source). Otherwise, the current path associated to the connected vertex is reset to
204              * null to allow the connected vertex addition to the priority queue later on with a new path
205              * (refer to "relaxSamcra" for addition of a connected vertex to the priority queue).
206              */
207             float previousLength = 1.0F;
208             CspfPath selectedPath = null;
209
210             if (!(currentPath.equals(pathSource))) {
211                 LOG.debug("Samcra: priority queue output processing for connected vertex:  {}",
212                         currentPath.getVertexKey());
213                 SamcraPath currentSamcraPath = samcraPaths.get(currentPath.getVertexKey());
214                 currentSamcraPath.decrementPathCount();
215                 /*
216                  * The list of paths associated to the connected vertex is retrieved
217                  * The path used to represent the connected vertex in the Priority Queue is marked from "selected"
218                  * to "processed". The list of paths is analyzed to check if other "active" path(s) exist(s).
219                  * If it is the case the shortest length is used to re-inject the connected vertex in the Priority Queue
220                  */
221                 for (CspfPath testedPath : currentSamcraPath.getPathList()) {
222                     LOG.debug("Samcra: priority queue output: testedPath: {} status: {} ", testedPath,
223                             testedPath.getPathStatus());
224                     if (testedPath.getPathStatus() == CspfPath.SELECTED) {
225                         testedPath.setPathStatus(CspfPath.PROCESSED);
226                     } else if ((testedPath.getPathStatus() == CspfPath.ACTIVE)
227                             && (testedPath.getPathLength() < previousLength)) {
228                         selectedPath = testedPath;
229                         previousLength = testedPath.getPathLength();
230                     }
231                 }
232                 /* If a path is found it is marked as "selected", used as "current path" for the connected vertex
233                  * and added to the priority queue
234                  */
235                 if (selectedPath != null) {
236                     selectedPath.setPathStatus(CspfPath.SELECTED);
237                     currentSamcraPath.setCurrentPath(selectedPath);
238                     priorityQueue.add(selectedPath);
239                     LOG.debug("Samcra priority queue output: add path to the priority queue: {} path count: {} ",
240                             selectedPath.getPath(), currentSamcraPath.getPathCount());
241                 } else {
242                     currentSamcraPath.setCurrentPath(null);
243                 }
244             }
245         }
246         /* The priority queue is empty => all the possible (vertex, path) elements have been explored
247          * The "ConstrainedPathBuilder" object contains the optimal path if it exists
248          * Otherwise an empty path with status failed is returned
249          */
250         if ((cpathBuilder.getStatus() == ComputationStatus.InProgress)
251                 || (cpathBuilder.getPathDescription().size() == 0)) {
252             cpathBuilder.setStatus(ComputationStatus.Failed);
253         } else {
254             cpathBuilder.setStatus(ComputationStatus.Completed);
255         }
256         return cpathBuilder.build();
257     }
258
259     /* Connected Edge to remote connected vertex processing (on contrast to CSPF algorithm, the already processed
260      * connected vertex are not zapped as a connected vertex may be associated to multiple paths). This method
261      * computes the TE metric and Delay costs up to the remote end-point connected vertex and checks if the computed
262      * values are acceptable according to the end-to-end constraints.
263      * If relevant, update the computed path on the remote end-point connected vertex.
264      * If the connected vertex has not already been processed, the corresponding CspfPath object is created.
265      */
266     private float relaxSamcra(ConnectedEdge edge, CspfPath currentPath, CspfPath source) {
267         LOG.debug("    Start SAMCRA relaxing Edge {} to Vertex {}", edge.toString(), edge.getDestination().toString());
268
269         /* Process CspfPath including the next Vertex */
270         CspfPath nextVertexPath = processedPath.get(edge.getDestination().getKey());
271         if (nextVertexPath == null) {
272             nextVertexPath = new CspfPath(edge.getDestination());
273             processedPath.put(nextVertexPath.getVertexKey(), nextVertexPath);
274             SamcraPath nextSamcraPath = new SamcraPath(edge.getDestination());
275             samcraPaths.put(nextVertexPath.getVertexKey(), nextSamcraPath);
276             LOG.debug("relaxSamcra: next connected vertex does not exists, create it: {} with new Samcra Path: {}",
277                     nextVertexPath.toString(), nextSamcraPath);
278         }
279
280         /* Connected Vertex's paths management using SamcraPath object.
281          * The predecessor connected vertex is checked to avoid unnecessary processing.
282          */
283         Long predecessorId = 0L;
284         if (!(currentPath.equals(source))) {
285             LOG.debug("relaxSamcra: check predecessor");
286             SamcraPath currentSamcraPath = samcraPaths.get(currentPath.getVertexKey());
287             CspfPath currentVertexPath = currentSamcraPath.getCurrentPath();
288             predecessorId = currentVertexPath.getPredecessor();
289         }
290         if (predecessorId.equals(nextVertexPath.getVertexKey())) {
291             LOG.debug("relaxSamcra: Skip Edge because next vertex: {} is predecessor of: {}",
292                     nextVertexPath.getVertexKey(), predecessorId);
293             return 0F;
294         }
295
296         /* Connected Vertex's paths management using CspfPath object.
297          * The paths list is explored and the paths dominated by the new path are marked as dominated.
298          * The new path is also check and if it is dominated by an existing path it is omitted.
299          */
300         if (edge.getEdge().getEdgeAttributes().getTeMetric() != null) {
301             teCost = edge.getEdge().getEdgeAttributes().getTeMetric().intValue() + currentPath.getCost();
302         } else {
303             teCost = currentPath.getCost();
304         }
305         if (edge.getEdge().getEdgeAttributes().getDelay() != null) {
306             delayCost = edge.getEdge().getEdgeAttributes().getDelay().getValue().intValue() + currentPath.getDelay();
307         } else {
308             delayCost = currentPath.getDelay();
309         }
310
311         SamcraPath samcraPath = samcraPaths.get(nextVertexPath.getVertexKey());
312         if (isPathDominated(samcraPath)) {
313             LOG.debug("relaxSamcra: Skip Edge because new path is dominated");
314             return 0F;
315         }
316
317         /* If the new path is not dominated by an already existing path, a new "CspfPath" object
318          * is created with predecessor set to connected vertex, path length and path status information,
319          * marked as "active" and added to the connected vertex's path list.
320          * The weight attribute, used as classification key by the priority queue, is an integer value computed
321          * from the TE and delay length.
322          */
323         CspfPath newPath = createNonDominatedPath(edge, nextVertexPath.getVertex(), currentPath);
324
325         /* The new path is check versus the path currently representing the connected vertex in the priority
326          * queue. If there is not yet a path for the connected vertex or if the new path length is shorter
327          * than the length of the path currently selected, the new path is used as current path, marked as
328          * "selected" and is added to the priority queue.
329          * The previously current path status is changed from "selected" to "active" and can be re-selected
330          * later on. If the new path is associated to the destination connected vertex it is not added to
331          * the priority queue.
332          */
333         CspfPath currentSamcraPath = samcraPath.getCurrentPath();
334         if (currentSamcraPath == null) {
335             LOG.debug("relaxSamcra: add new Path: {}", newPath);
336             if (!(newPath.equals(pathDestination))) {
337                 priorityQueue.add(newPath);
338             }
339             newPath.setPathStatus(CspfPath.SELECTED);
340             samcraPath.setCurrentPath(newPath);
341         } else if (newPath.getPathLength() < currentSamcraPath.getPathLength()) {
342             LOG.debug("relaxSamcra: update current Path: {} with new Path: {}", currentSamcraPath, newPath);
343             samcraPath.getPathList()
344                 .stream()
345                 .filter(path -> path.getPathStatus() == CspfPath.SELECTED)
346                 .forEach(path -> path.setPathStatus(CspfPath.ACTIVE));
347
348             /* It is not possible to directly update the CspfPath in the Priority Queue. Indeed, if we
349              * modify the path weight, the Priority Queue must be re-ordered. So, we need fist to remove
350              * the CspfPath if it is present in the Priority Queue, then, update the Path Weight,
351              * and finally (re-)insert it in the Priority Queue.
352              */
353             if (!(newPath.equals(pathDestination))) {
354                 priorityQueue.removeIf((path) -> path.getVertexKey().equals(newPath.getVertexKey()));
355                 priorityQueue.add(newPath);
356             }
357             newPath.setPathStatus(CspfPath.SELECTED);
358             samcraPath.setCurrentPath(newPath);
359         }
360
361         /* In all cases the new path is added to the list of paths associated to the vertex */
362         samcraPath.addPath(newPath);
363         samcraPath.incrementPathCount();
364
365         LOG.debug("relaxSamcra: number of paths  {} ", samcraPath.getPathCount());
366         LOG.debug("relaxSamcra: add vertex Paths to samcraPath {} with index {}", samcraPath,
367                 samcraPath.getVertex().getKey());
368         LOG.debug("relaxSamcra: current Path {}  predecessor {}",  samcraPath.getCurrentPath(),
369                 samcraPath.getCurrentPath().getPredecessor());
370         samcraPaths.put(samcraPath.getVertex().getKey(), samcraPath);
371
372         /* If the destination is reached, return the computed path length 0 otherwise */
373         if ((samcraPath.getVertex().getKey()).equals(pathDestination.getVertexKey())) {
374             return samcraPath.getCurrentPath().getPathLength();
375         } else {
376             return 0F;
377         }
378     }
379
380     /**
381      * Evaluate if the current path is dominated by an all one or dominates all previous computed path.
382      *
383      * @param samcraPath Current Samcra Path
384      *
385      * @return true if path is dominated false otherwise
386      */
387     private boolean isPathDominated(SamcraPath samcraPath) {
388         /* Evaluate Path Domination */
389         LOG.debug("Check path domination");
390         Uint32 teMetric = constraints.getTeMetric();
391         Uint32 delay = (constraints.getDelay() != null) ? constraints.getDelay().getValue() : null;
392
393         for (CspfPath testedPath : samcraPath.getPathList()) {
394             boolean pathCostDominated = false;
395             boolean pathDelayDominated = false;
396             boolean testedPathCostDominated = false;
397             boolean testedPathDelayDominated = false;
398
399             LOG.debug("Check if path {} is dominated or dominates", testedPath);
400             if (testedPath.getPathStatus() != CspfPath.DOMINATED) {
401                 if (teMetric != null) {
402                     if (teCost >= testedPath.getCost()) {
403                         pathCostDominated = true;
404                     } else {
405                         testedPathCostDominated = true;
406                     }
407                 }
408                 if (delay != null) {
409                     if (delayCost >= testedPath.getDelay()) {
410                         pathDelayDominated = true;
411                     } else {
412                         testedPathDelayDominated = true;
413                     }
414                 }
415
416                 if ((((teMetric != null) && (pathCostDominated)) && ((pathDelayDominated) || (delay == null)))
417                         || ((teMetric == null) && ((delay != null) && (pathDelayDominated)))) {
418                     LOG.debug("New path is dominated with teCost: {} delayCost: {}", teCost, delayCost);
419                     /* A path that dominates the current path has been found */
420                     return true;
421                 } else if ((((teMetric != null) && (testedPathCostDominated))
422                         && ((testedPathDelayDominated) || (delay == null)))
423                         || ((teMetric == null) && ((delay != null) && (testedPathDelayDominated)))) {
424                     /* Old Path is dominated by the new path. Mark it as Dominated and decrement number of valid Path */
425                     testedPath.setPathStatus(CspfPath.DOMINATED);
426                     samcraPath.decrementPathCount();
427                     LOG.debug("New path dominates existing path: {} teCost: {} delayCost:  {}", testedPath,
428                             testedPath.getCost(), testedPath.getDelay());
429                 }
430             }
431         }
432         return false;
433     }
434
435     private CspfPath createNonDominatedPath(ConnectedEdge edge, ConnectedVertex vertex, CspfPath cspfPath) {
436         float pathLength = 1.0F;
437         Uint32 metric = constraints.getTeMetric();
438         Uint32 delay = (constraints.getDelay() != null) ? constraints.getDelay().getValue() : null;
439
440         LOG.debug("Create new non dominated path");
441
442         /* Compute Path length as key for the path Weight */
443         float teLength = 0.0F;
444         if ((metric != null) && (metric.intValue() > 0)) {
445             teLength = (float) teCost / metric.intValue();
446             pathLength = teLength;
447         }
448         float delayLength = 0.0F;
449         if ((delay != null) && (delay.intValue() > 0)) {
450             delayLength = (float) delayCost / delay.intValue();
451             if (delayLength > teLength) {
452                 pathLength = delayLength;
453             }
454         }
455
456         /* Create new Path with computed TE Metric, Delay and Path Length */
457         CspfPath newPath = new CspfPath(vertex)
458                 .setCost(teCost)
459                 .setDelay(delayCost)
460                 .setKey((int) (100 * pathLength))
461                 .setPathStatus(CspfPath.ACTIVE)
462                 .setPathLength(pathLength)
463                 .setPredecessor(cspfPath.getVertexKey())
464                 .replacePath(cspfPath.getPath())
465                 .addConnectedEdge(edge);
466
467         LOG.debug("Creation of a new Path: {} path length: {} predecessor connected vertex {}",
468                 newPath, pathLength, newPath.getPredecessor());
469
470         return cspfPath;
471     }
472 }