Merge changes I082f82ac,I951224b0,Icecd1b21,Ifafa74b6,I9b384c72, ...
[transportpce.git] / pce / src / main / java / org / opendaylight / transportpce / pce / graph / PostAlgoPathValidator.java
1 /*
2  * Copyright © 2017 AT&T, Inc. and others.  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
9 package org.opendaylight.transportpce.pce.graph;
10
11 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
12 import java.math.BigDecimal;
13 import java.math.RoundingMode;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.BitSet;
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.concurrent.ExecutionException;
22 import java.util.stream.Collectors;
23 import org.jgrapht.GraphPath;
24 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
25 import org.opendaylight.transportpce.common.InstanceIdentifiers;
26 import org.opendaylight.transportpce.common.ResponseCodes;
27 import org.opendaylight.transportpce.common.StringConstants;
28 import org.opendaylight.transportpce.common.catalog.CatalogConstant;
29 import org.opendaylight.transportpce.common.catalog.CatalogConstant.CatalogNodeType;
30 import org.opendaylight.transportpce.common.catalog.CatalogUtils;
31 import org.opendaylight.transportpce.common.fixedflex.GridConstant;
32 import org.opendaylight.transportpce.common.fixedflex.GridUtils;
33 import org.opendaylight.transportpce.common.network.NetworkTransactionService;
34 import org.opendaylight.transportpce.pce.constraints.PceConstraints;
35 import org.opendaylight.transportpce.pce.constraints.PceConstraints.ResourcePair;
36 import org.opendaylight.transportpce.pce.networkanalyzer.PceLink;
37 import org.opendaylight.transportpce.pce.networkanalyzer.PceNode;
38 import org.opendaylight.transportpce.pce.networkanalyzer.PceResult;
39 import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev220808.SpectrumAssignment;
40 import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev220808.SpectrumAssignmentBuilder;
41 import org.opendaylight.yang.gen.v1.http.org.openroadm.network.topology.rev211210.TerminationPoint1;
42 import org.opendaylight.yang.gen.v1.http.org.openroadm.network.types.rev211210.OpenroadmLinkType;
43 import org.opendaylight.yang.gen.v1.http.org.openroadm.network.types.rev211210.OpenroadmNodeType;
44 import org.opendaylight.yang.gen.v1.http.org.openroadm.otn.common.types.rev210924.OpucnTribSlotDef;
45 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.network.rev180226.NodeId;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.network.topology.rev180226.LinkId;
47 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
48 import org.opendaylight.yangtools.yang.common.Uint16;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 public class PostAlgoPathValidator {
53     /* Logging. */
54     private static final Logger LOG = LoggerFactory.getLogger(PostAlgoPathValidator.class);
55
56     public static final Long CONST_OSNR = 1L;
57     public static final double SYS_MARGIN = 0;
58     private Double tpceCalculatedMargin = 0.0;
59     private final NetworkTransactionService networkTransactionService;
60
61     public PostAlgoPathValidator(NetworkTransactionService networkTransactionService) {
62         this.networkTransactionService = networkTransactionService;
63     }
64
65     @SuppressWarnings("fallthrough")
66     @SuppressFBWarnings(
67         value = "SF_SWITCH_FALLTHROUGH",
68         justification = "intentional fallthrough")
69
70     public PceResult checkPath(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
71             Map<LinkId, PceLink> allPceLinks, PceResult pceResult, PceConstraints pceHardConstraints,
72             String serviceType) {
73         LOG.info("path = {}", path);
74         // check if the path is empty
75         if (path.getEdgeList().isEmpty()) {
76             pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
77             return pceResult;
78         }
79         int spectralWidthSlotNumber =
80             GridConstant.SPECTRAL_WIDTH_SLOT_NUMBER_MAP.getOrDefault(serviceType, GridConstant.NB_SLOTS_100G);
81         SpectrumAssignment spectrumAssignment = null;
82         //variable to deal with 1GE (Nb=1) and 10GE (Nb=10) cases
83         switch (serviceType) {
84             case StringConstants.SERVICE_TYPE_OTUC2:
85             case StringConstants.SERVICE_TYPE_OTUC3:
86             case StringConstants.SERVICE_TYPE_OTUC4:
87             case StringConstants.SERVICE_TYPE_400GE:
88                 spectralWidthSlotNumber =
89                     GridConstant.SPECTRAL_WIDTH_SLOT_NUMBER_MAP.getOrDefault(serviceType, GridConstant.NB_SLOTS_400G);
90             //fallthrough
91             case StringConstants.SERVICE_TYPE_100GE_T:
92             case StringConstants.SERVICE_TYPE_OTU4:
93                 spectrumAssignment = getSpectrumAssignment(path, allPceNodes, spectralWidthSlotNumber);
94                 pceResult.setServiceType(serviceType);
95                 if (spectrumAssignment.getBeginIndex().equals(Uint16.valueOf(0))
96                         && spectrumAssignment.getStopIndex().equals(Uint16.valueOf(0))) {
97                     pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
98                     pceResult.setLocalCause(PceResult.LocalCause.NO_PATH_EXISTS);
99                     return pceResult;
100                 }
101                 if (spectrumAssignment.getFlexGrid()) {
102                     LOG.debug("Spectrum assignment flexgrid mode");
103                     pceResult.setResultWavelength(GridConstant.IRRELEVANT_WAVELENGTH_NUMBER);
104                 } else {
105                     LOG.debug("Spectrum assignment fixedgrid mode");
106                     pceResult.setResultWavelength(
107                         GridUtils.getWaveLengthIndexFromSpectrumAssigment(spectrumAssignment.getBeginIndex().toJava()));
108                 }
109                 pceResult.setMinFreq(GridUtils.getStartFrequencyFromIndex(spectrumAssignment.getBeginIndex().toJava()));
110                 pceResult.setMaxFreq(GridUtils.getStopFrequencyFromIndex(spectrumAssignment.getStopIndex().toJava()));
111                 LOG.debug("In PostAlgoPathValidator: spectrum assignment found {} {}", spectrumAssignment, path);
112
113                 // Check the OSNR
114                 CatalogUtils cu = new CatalogUtils(networkTransactionService);
115                 if (cu.isCatalogFilled()) {
116                     double margin1 = checkOSNR(path, allPceNodes, allPceLinks, serviceType,
117                             StringConstants.SERVICE_DIRECTION_AZ, cu);
118                     double margin2 = checkOSNR(path, allPceNodes, allPceLinks, serviceType,
119                             StringConstants.SERVICE_DIRECTION_ZA, cu);
120                     if (margin1 < 0 || margin2 < 0 || margin1 == Double.NEGATIVE_INFINITY
121                             || margin2 == Double.NEGATIVE_INFINITY) {
122                         pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
123                         pceResult.setLocalCause(PceResult.LocalCause.OUT_OF_SPEC_OSNR);
124                         return pceResult;
125                     }
126                     this.tpceCalculatedMargin = Math.min(margin1, margin2);
127                     LOG.info(
128                         "In PostAlgoPathValidator: Minimum margin estimated by tpce on AtoZ and ZtoA path is of  {} dB",
129                         this.tpceCalculatedMargin);
130                 } else {
131                     this.tpceCalculatedMargin = 0.0;
132                     LOG.info("In PostAlgoPathValidator: Operational mode Catalog not filled, delegate OSNR calculation"
133                         + " to GNPy and margin set to 0");
134                 }
135                 // Check if MaxLatency is defined in the hard constraints
136                 if (pceHardConstraints.getMaxLatency() != -1
137                         && !checkLatency(pceHardConstraints.getMaxLatency(), path)) {
138                     pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
139                     pceResult.setLocalCause(PceResult.LocalCause.TOO_HIGH_LATENCY);
140                     return pceResult;
141                 }
142                 // Check if nodes are included in the hard constraints
143                 if (!checkInclude(path, pceHardConstraints)) {
144                     pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
145                     pceResult.setLocalCause(PceResult.LocalCause.HD_NODE_INCLUDE);
146                     return pceResult;
147                 }
148                 // TODO here other post algo validations can be added
149                 // more data can be sent to PceGraph module via PceResult structure if required
150                 pceResult.setRC(ResponseCodes.RESPONSE_OK);
151                 pceResult.setLocalCause(PceResult.LocalCause.NONE);
152                 return pceResult;
153             case StringConstants.SERVICE_TYPE_100GE_M:
154             case StringConstants.SERVICE_TYPE_10GE:
155             case StringConstants.SERVICE_TYPE_1GE:
156                 int tribSlotNb = Map.of(
157                         StringConstants.SERVICE_TYPE_100GE_M, 20,
158                         StringConstants.SERVICE_TYPE_10GE, 8,
159                         StringConstants.SERVICE_TYPE_1GE, 1)
160                     .get(serviceType);
161                 pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
162                 pceResult.setServiceType(serviceType);
163                 Map<String, List<Uint16>> tribSlot = chooseTribSlot(path, allPceNodes, tribSlotNb);
164                 Map<String, Uint16> tribPort = chooseTribPort(path, allPceNodes, tribSlot, tribSlotNb);
165                 List<OpucnTribSlotDef> resultTribPortTribSlot = getMinMaxTpTs(tribPort, tribSlot);
166                 if (resultTribPortTribSlot.get(0) != null && resultTribPortTribSlot.get(1) != null) {
167                     pceResult.setResultTribPortTribSlot(resultTribPortTribSlot);
168                     pceResult.setRC(ResponseCodes.RESPONSE_OK);
169                     LOG.info("In PostAlgoPathValidator: found TribPort {} - tribSlot {} - tribSlotNb {}",
170                         tribPort, tribSlot, tribSlotNb);
171                 }
172                 return pceResult;
173             case StringConstants.SERVICE_TYPE_ODU4:
174             case StringConstants.SERVICE_TYPE_ODUC2:
175             case StringConstants.SERVICE_TYPE_ODUC3:
176             case StringConstants.SERVICE_TYPE_ODUC4:
177             case StringConstants.SERVICE_TYPE_100GE_S:
178                 pceResult.setRC(ResponseCodes.RESPONSE_OK);
179                 pceResult.setServiceType(serviceType);
180                 LOG.info("In PostAlgoPathValidator: ODU4/ODUCn path found {}", path);
181                 return pceResult;
182             default:
183                 pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
184                 LOG.warn("In PostAlgoPathValidator checkPath: unsupported serviceType {} found {}",
185                     serviceType, path);
186                 return pceResult;
187         }
188     }
189
190     // Check the latency
191     private boolean checkLatency(Long maxLatency, GraphPath<String, PceGraphEdge> path) {
192         double latency = 0;
193         for (PceGraphEdge edge : path.getEdgeList()) {
194             if (edge.link() == null || edge.link().getLatency() == null) {
195                 LOG.warn("- In checkLatency: the link {} does not contain latency field",
196                     edge.link().getLinkId().getValue());
197                 return false;
198             }
199             latency += edge.link().getLatency();
200             LOG.debug("- In checkLatency: latency of {} = {} units", edge.link().getLinkId().getValue(), latency);
201         }
202         return (latency < maxLatency);
203     }
204
205     // Check the inclusion if it is defined in the hard constraints
206     private boolean checkInclude(GraphPath<String, PceGraphEdge> path, PceConstraints pceHardConstraintsInput) {
207         List<ResourcePair> listToInclude = pceHardConstraintsInput.getListToInclude()
208             .stream().sorted((rp1, rp2) -> rp1.getName().compareTo(rp2.getName()))
209             .collect(Collectors.toList());
210         if (listToInclude.isEmpty()) {
211             return true;
212         }
213         List<PceGraphEdge> pathEdges = path.getEdgeList();
214         LOG.debug(" in checkInclude vertex list: [{}]", path.getVertexList());
215         List<String> listOfElementsSubNode = new ArrayList<>();
216         listOfElementsSubNode.add(pathEdges.get(0).link().getsourceNetworkSupNodeId());
217         listOfElementsSubNode.addAll(
218             listOfElementsBuild(pathEdges, PceConstraints.ResourceType.NODE, pceHardConstraintsInput));
219         List<String> listOfElementsCLLI = new ArrayList<>();
220         listOfElementsCLLI.add(pathEdges.get(0).link().getsourceCLLI());
221         listOfElementsCLLI.addAll(
222             listOfElementsBuild(pathEdges, PceConstraints.ResourceType.CLLI, pceHardConstraintsInput));
223         List<String> listOfElementsSRLG = new ArrayList<>();
224         // first link is XPONDEROUTPUT, no SRLG for it
225         listOfElementsSRLG.add("NONE");
226         listOfElementsSRLG.addAll(
227             listOfElementsBuild(pathEdges, PceConstraints.ResourceType.SRLG, pceHardConstraintsInput));
228         // validation: check each type for each element
229         return listOfElementsSubNode.containsAll(
230                 listToInclude
231                     .stream().filter(rp -> PceConstraints.ResourceType.NODE.equals(rp.getType()))
232                     .map(ResourcePair::getName).collect(Collectors.toList()))
233             && listOfElementsSRLG.containsAll(
234                 listToInclude
235                     .stream().filter(rp -> PceConstraints.ResourceType.SRLG.equals(rp.getType()))
236                     .map(ResourcePair::getName).collect(Collectors.toList()))
237             && listOfElementsCLLI.containsAll(
238                 listToInclude
239                     .stream().filter(rp -> PceConstraints.ResourceType.CLLI.equals(rp.getType()))
240                     .map(ResourcePair::getName).collect(Collectors.toList()));
241     }
242
243     private List<String> listOfElementsBuild(List<PceGraphEdge> pathEdges, PceConstraints.ResourceType type,
244             PceConstraints pceHardConstraints) {
245         List<String> listOfElements = new ArrayList<>();
246         for (PceGraphEdge link : pathEdges) {
247             switch (type) {
248                 case NODE:
249                     listOfElements.add(link.link().getdestNetworkSupNodeId());
250                     break;
251                 case CLLI:
252                     listOfElements.add(link.link().getdestCLLI());
253                     break;
254                 case SRLG:
255                     if (link.link().getlinkType() != OpenroadmLinkType.ROADMTOROADM) {
256                         listOfElements.add("NONE");
257                         break;
258                     }
259                     // srlg of link is List<Long>. But in this algo we need string representation of
260                     // one SRLG
261                     // this should be any SRLG mentioned in include constraints if any of them if
262                     // mentioned
263                     boolean found = false;
264                     for (Long srlg : link.link().getsrlgList()) {
265                         String srlgStr = String.valueOf(srlg);
266                         if (pceHardConstraints.getSRLGnames().contains(srlgStr)) {
267                             listOfElements.add(srlgStr);
268                             LOG.info("listOfElementsBuild. FOUND SRLG {} in link {}", srlgStr, link.link());
269                             found = true;
270                         }
271                     }
272                     if (!found) {
273                         // there is no specific srlg to include. thus add to list just the first one
274                         listOfElements.add("NONE");
275                     }
276                     break;
277                 default:
278                     LOG.debug("listOfElementsBuild unsupported resource type");
279             }
280         }
281         return listOfElements;
282     }
283
284     private Map<String, Uint16> chooseTribPort(GraphPath<String,
285             PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes, Map<String, List<Uint16>> tribSlotMap, int nbSlot) {
286         LOG.debug("In choosetribPort: edgeList = {} ", path.getEdgeList());
287         Map<String, Uint16> tribPortMap = new HashMap<>();
288         for (PceGraphEdge edge : path.getEdgeList()) {
289             List<Uint16> srcTpnPool =
290                 allPceNodes
291                     .get(edge.link().getSourceId())
292                     .getAvailableTribPorts()
293                     .get(edge.link().getSourceTP().getValue());
294             List<Uint16> destTpnPool =
295                 allPceNodes
296                     .get(edge.link().getDestId())
297                     .getAvailableTribPorts()
298                     .get(edge.link().getDestTP().getValue());
299             List<Uint16> commonEdgeTpnPool = new ArrayList<>();
300             for (Uint16 srcTpn : srcTpnPool) {
301                 if (destTpnPool.contains(srcTpn)) {
302                     commonEdgeTpnPool.add(srcTpn);
303                 }
304             }
305             if (commonEdgeTpnPool.isEmpty()) {
306                 continue;
307             }
308             Integer startTribSlot = tribSlotMap.values().stream().findFirst().get().get(0).toJava();
309             Integer tribPort = (int) Math.ceil((double)startTribSlot / nbSlot);
310             for (Uint16 commonTribPort : commonEdgeTpnPool) {
311                 if (tribPort.equals(commonTribPort.toJava())) {
312                     tribPortMap.put(edge.link().getLinkId().getValue(), commonTribPort);
313                 }
314             }
315         }
316         tribPortMap.forEach((k,v) -> LOG.info("TribPortMap : k = {}, v = {}", k, v));
317         return tribPortMap;
318     }
319
320     private Map<String, List<Uint16>> chooseTribSlot(GraphPath<String,
321             PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes, int nbSlot) {
322         LOG.debug("In choosetribSlot: edgeList = {} ", path.getEdgeList());
323         Map<String, List<Uint16>> tribSlotMap = new HashMap<>();
324         for (PceGraphEdge edge : path.getEdgeList()) {
325             List<Uint16> srcTsPool =
326                 allPceNodes
327                     .get(edge.link().getSourceId())
328                     .getAvailableTribSlots()
329                     .get(edge.link().getSourceTP().getValue());
330             List<Uint16> destTsPool =
331                 allPceNodes
332                     .get(edge.link().getDestId())
333                     .getAvailableTribSlots()
334                     .get(edge.link().getDestTP().getValue());
335             List<Uint16> commonEdgeTsPoolList = new ArrayList<>();
336             List<Uint16> tribSlotList = new ArrayList<>();
337             for (Uint16 integer : srcTsPool) {
338                 if (destTsPool.contains(integer)) {
339                     commonEdgeTsPoolList.add(integer);
340                 }
341             }
342             Collections.sort(commonEdgeTsPoolList);
343             List<Uint16> commonGoodStartEdgeTsPoolList = new ArrayList<>();
344             for (Uint16 startEdgeTsPool : commonEdgeTsPoolList) {
345                 if (Integer.valueOf(1).equals(startEdgeTsPool.toJava() % nbSlot) || nbSlot == 1) {
346                     commonGoodStartEdgeTsPoolList.add(startEdgeTsPool);
347                 }
348             }
349             Collections.sort(commonGoodStartEdgeTsPoolList);
350             boolean goodTsList = false;
351             for (Uint16 goodStartTsPool : commonGoodStartEdgeTsPoolList) {
352                 int goodStartIndex = commonEdgeTsPoolList.indexOf(Uint16.valueOf(goodStartTsPool.intValue()));
353                 if (!goodTsList && commonEdgeTsPoolList.size() - goodStartIndex >= nbSlot) {
354                     for (int i = 0; i < nbSlot; i++) {
355                         if (!commonEdgeTsPoolList.get(goodStartIndex + i)
356                                 .equals(Uint16.valueOf(goodStartTsPool.toJava() + i))) {
357                             goodTsList = false;
358                             tribSlotList.clear();
359                             break;
360                         }
361                         tribSlotList.add(commonEdgeTsPoolList.get(goodStartIndex + i));
362                         goodTsList = true;
363                     }
364                 }
365             }
366             tribSlotMap.put(edge.link().getLinkId().getValue(), tribSlotList);
367         }
368         tribSlotMap.forEach((k,v) -> LOG.info("TribSlotMap : k = {}, v = {}", k, v));
369         return tribSlotMap;
370     }
371
372     private List<OpucnTribSlotDef> getMinMaxTpTs(Map<String, Uint16> tribPort, Map<String, List<Uint16>> tribSlot) {
373         String tribport = tribPort.values().toArray()[0].toString();
374         List<Uint16> tsList = (List<Uint16>) tribSlot.values().toArray()[0];
375         return new ArrayList<>(List.of(
376             OpucnTribSlotDef.getDefaultInstance(String.join(".", tribport, tsList.get(0).toString())),
377             OpucnTribSlotDef.getDefaultInstance(String.join(".", tribport, tsList.get(tsList.size() - 1).toString()))));
378     }
379
380     /**
381      * Calculates the OSNR of a path, according to the direction (AtoZ/ZtoA), using the operational-modes Catalog.
382      *
383      * @param path                      the AtoZ path provided by the PCE.
384      * @param allPceNode                The map of chosen/relevant PceNodes build from topology pruning.
385      * @param allPceLinks               The map of PceLinks build corresponding to the whole topology.
386      * @param serviceType               The service Type used to extrapolate Operational mode when it is not provided.
387      * @param direction                 The direction used to scan provided path in a direct or reverse way.
388      * @param cu                        CatalogUtils instance.
389      * @return the calculated margin according to the Transponder performances and path impairments.
390      */
391     @SuppressWarnings("deprecation")
392     @edu.umd.cs.findbugs.annotations.SuppressWarnings("DLS_DEAD_LOCAL_STORE")
393     private double checkOSNR(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
394             Map<LinkId, PceLink> allPceLinks, String serviceType, String direction, CatalogUtils cu) {
395         double spacing = 50.0;
396         double calcPdl2 = 0;
397         double calcOsnrdB = 0;
398         double calcCd = 0;
399         double calcPmd2 = 0;
400         double calcOnsrLin = 0.0001;
401         double margin = 0;
402         double pwrIn = -60.0;
403         double pwrOut = -60.0;
404         int increment = 1;
405         int offsetLink = 0;
406         boolean transponderPresent = false;
407         if (direction.equals(StringConstants.SERVICE_DIRECTION_ZA)) {
408             increment = - 1;
409             offsetLink = -1;
410         }
411         CatalogNodeType cnt;
412         List<String> vertices = path.getVertexList();
413         List<PceGraphEdge> edges = path.getEdgeList();
414         String opMode = "";
415         // LOOP that scans the different Nodes/Links of the path and calculates
416         // associated degradations
417         // using CatalogUtils primitives to retrieve physical parameters and make a
418         // first level calculation
419         Map<String, Double> impairments = new HashMap<>();
420         for (int n = 0; n < vertices.size(); n++) {
421             int pathElement = direction.equals(StringConstants.SERVICE_DIRECTION_AZ) ? n : vertices.size() - n - 1;
422             PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(pathElement)));
423             PceNode nextNode =
424                 pathElement != vertices.size() - 1 && direction.equals(StringConstants.SERVICE_DIRECTION_AZ)
425                         || pathElement != 0 && direction.equals(StringConstants.SERVICE_DIRECTION_ZA)
426                     ? allPceNodes.get(new NodeId(vertices.get(pathElement + increment)))
427                     : null;
428             LOG.debug("loop of check OSNR, n = {} Path Element = {}", n, pathElement);
429             switch (currentNode.getORNodeType()) {
430                 case XPONDER:
431                     transponderPresent = true;
432                     String nwTpId =
433                             pathElement == 0 && direction.equals(StringConstants.SERVICE_DIRECTION_AZ)
434                                     || pathElement == vertices.size() - 1
435                                         && direction.equals(StringConstants.SERVICE_DIRECTION_ZA)
436                                 ? getAppropriatePceLink((pathElement + offsetLink), edges, allPceLinks, direction)
437                                     .getSourceTP()
438                                     .getValue()
439                         // last Xponder of the path (RX side)
440                                 : getAppropriatePceLink((pathElement - offsetLink - 1), edges, allPceLinks, direction)
441                                     .getDestTP()
442                                     .getValue();
443                     InstanceIdentifier<TerminationPoint1> nwTpIid =
444                         InstanceIdentifiers.createNetworkTerminationPoint1IIDBuilder(vertices.get(pathElement), nwTpId);
445                     LOG.debug("loop of check OSNR : XPDR, n = {} Path Element = {}", n, pathElement);
446                     try {
447                         if (networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, nwTpIid)
448                                 .get().isPresent()) {
449 // If the Xponder operational mode (setOpMode Arg1) is not consistent or not declared in the topology (Network TP)
450 // Operational mode is retrieved from the service Type assuming it is supported by the Xponder (setOpMode Arg2)
451                             opMode = setOpMode(
452                                 currentNode.getXponderOperationalMode(
453                                     networkTransactionService
454                                             .read(LogicalDatastoreType.CONFIGURATION, nwTpIid)
455                                             .get().get().getXpdrNetworkAttributes()),
456                                 // Operational mode is found as an attribute of the network TP
457                                 cu.getPceOperationalModeFromServiceType(
458                                     CatalogConstant.CatalogNodeType.TSP, serviceType));
459                                 // Operational mode is retrieved from the service Type assuming it is supported
460                                 // by the Xponder
461                             LOG.debug("Transponder {} corresponding to path Element {} in the path has {} operational "
462                                     + "mode", currentNode.getNodeId().getValue(), pathElement, opMode);
463                         } else {
464                             LOG.error("Issue accessing the XponderNetworkAttributes of {} for Transponder {}"
465                                 + " corresponding to path Element {} in the path ",
466                                 nwTpId, currentNode.getNodeId().getValue(), pathElement);
467                             opMode = cu.getPceOperationalModeFromServiceType(
468                                 CatalogConstant.CatalogNodeType.TSP, serviceType);
469                             LOG.info("Did not succeed finding network TP {} in Configuration Datastore. Retrieve"
470                                 + " default Operational Mode {} from serviceType {}", nwTpId, opMode, serviceType);
471                         }
472                     } catch (InterruptedException | ExecutionException e1) {
473                         opMode = cu.getPceOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.TSP,
474                             serviceType);
475                         LOG.info("Did not succeed finding network TP {} in Configuration Datastore. Retrieve"
476                             + " default Operational Mode {} from serviceType {}", nwTpId, opMode, serviceType);
477                     }
478                     // If TSP is the last of the path
479                     if (pathElement == vertices.size() - 1 && direction.equals(StringConstants.SERVICE_DIRECTION_AZ)
480                             || pathElement == 0 && direction.equals(StringConstants.SERVICE_DIRECTION_ZA)) {
481                         LOG.debug("Loop n = {}, Step5.1, XPDR, tries calculating Margin, just before call", n);
482                         // Check that accumulated degradations are compatible with TSP performances
483                         // According to OpenROADM spec :
484                         // margin = cu.getPceRxTspParameters(opMode, calcCd, Math.sqrt(calcPmd2), Math.sqrt(calcPdl2),
485                         //              getOsnrDbfromOnsrLin(calcOnsrLin));
486                         // Calculation modified for pdl according to calculation in Julia's Tool
487                         margin = cu.getPceRxTspParameters(opMode, calcCd, Math.sqrt(calcPmd2),
488                             (Math.sqrt(calcPdl2)), getOsnrDbfromOnsrLin(calcOnsrLin));
489                         LOG.info("Loop n = {}, XPDR, calcosnrdB= {}", n, getOsnrDbfromOnsrLin(calcOnsrLin));
490                     } else {
491                         // TSP is first element of the path . To correctly evaluate the TX OOB OSNR from
492                         // its operational mode, we need to know the type of ADD/DROP Mux it is
493                         // connected to
494                         String adnMode = "";
495                         // If the operational mode of the ADD/DROP MUX is not consistent or
496                         // if the operational mode of the ADD/DROP MUX is not declared in the topology
497                         // (Network TP)
498                             // Operational mode is set by default to standard opMode for ADD SRGs
499                         adnMode = setOpMode(nextNode.getOperationalMode(), CatalogConstant.MWWRCORE);
500                             // Operational mode is found in SRG attributes of the Node
501                         LOG.debug("Transponder {} corresponding to path Element {} in the path is connected to SRG "
502                             + "which has {} operational mode", currentNode.getNodeId().getValue(), pathElement,
503                             adnMode);
504                         // Retrieve the Tx ONSR of the Xponder which results from IB and OOB OSNR
505                         // contributions
506                         calcOnsrLin = cu.getPceTxTspParameters(opMode, adnMode);
507                         // Retrieve the spacing associated with Xponder operational mode that is needed
508                         // to calculate OSNR
509                         spacing = cu.getPceTxTspChannelSpacing(opMode);
510                         LOG.info("Transponder {} corresponding to path Element {} in the path has a TX OSNR of {} dB",
511                             currentNode.getNodeId().getValue(), pathElement, getOsnrDbfromOnsrLin(calcOnsrLin));
512                     }
513                     break;
514                 case SRG:
515 // If the operational mode of the ADD/DROP MUX is not consistent or is not declared in the topology (Network TP)
516 // Operational mode is set by default to standard opMode for ADD/DROP SRGs
517                     String srgMode = setOpMode(currentNode.getOperationalMode(), CatalogConstant.MWWRCORE);
518                     cnt = CatalogConstant.CatalogNodeType.DROP;
519                     LOG.debug("loop of check OSNR : SRG, n = {} Path Element = {}", n, pathElement);
520                     if (pathElement <= 1 && direction.equals(StringConstants.SERVICE_DIRECTION_AZ)
521                             || pathElement >= vertices.size() - 2
522                                 && direction.equals(StringConstants.SERVICE_DIRECTION_ZA)) {
523                         // This is ADD case : First (optical-tunnel) or 2nd (Regular E2E service from
524                         // Xponder to Xponder) node element of the path is the ADD SRG.
525                         if (getAppropriatePceLink(pathElement + offsetLink, edges, allPceLinks, direction)
526                                 .getlinkType() != OpenroadmLinkType.ADDLINK) {
527                             LOG.error("Error processing Node {} for which output link {} is not an ADDLINK Type",
528                                 currentNode.getNodeId().toString(), pathElement + offsetLink);
529                         }
530                         cnt = CatalogConstant.CatalogNodeType.ADD;
531                         pwrIn = 0.0;
532                         pwrOut = cu.getPceRoadmAmpOutputPower(cnt, srgMode,
533                             getAppropriatePceLink((pathElement + 1 + offsetLink * 3), edges, allPceLinks, direction)
534                                 .getspanLoss(),
535                             spacing,
536                             getAppropriatePceLink((pathElement + 1 + offsetLink * 3), edges, allPceLinks, direction)
537                                 .getpowerCorrection());
538                         LOG.debug("loop of check OSNR : SRG, n = {} link {} Pout = {}",
539                             pathElement, pathElement + 1 + offsetLink * 3, pwrOut);
540                     } else {
541                         // Other case is DROP, for which cnt is unchanged (.DROP)
542                         if (getAppropriatePceLink(pathElement - 1 - offsetLink, edges, allPceLinks, direction)
543                                 .getlinkType() != OpenroadmLinkType.DROPLINK) {
544                             LOG.error("Error processing Node {} for which input link {} is not a DROPLINK Type",
545                                 currentNode.getNodeId().toString(), pathElement - 1 - offsetLink);
546                         }
547                         pwrIn = pwrOut - getAppropriatePceLink((pathElement - offsetLink * 3 - 2), edges, allPceLinks,
548                             direction).getspanLoss();
549                         // Calculate degradation accumulated across incoming Link and add them to
550                         // accumulated impairments
551                         calcCd += getAppropriatePceLink((pathElement - offsetLink * 3 - 2), edges, allPceLinks,
552                             direction).getcd();
553                         LOG.info("loop of check OSNR : SRG, n = {} CD on preceeding link {} = {} ps", pathElement,
554                             pathElement - offsetLink * 3 - 2, getAppropriatePceLink((pathElement - offsetLink * 3 - 2),
555                                 edges, allPceLinks, direction).getcd());
556                         calcPmd2 += getAppropriatePceLink((pathElement - offsetLink * 3 - 2), edges, allPceLinks,
557                             direction).getpmd2();
558                         // This also includes Non Linear Contribution from the path
559                         calcOnsrLin += cu.calculateNLonsrContribution(pwrOut, getAppropriatePceLink((pathElement
560                             - offsetLink * 3 - 2), edges, allPceLinks, direction).getLength(), spacing);
561                     }
562                     //calculation of the SRG contribution either for Add and Drop
563                     impairments = cu.getPceRoadmAmpParameters(cnt, srgMode,
564                         pwrIn, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
565                     calcCd = impairments.get("CD").doubleValue();
566                     calcPmd2 = impairments.get("DGD2").doubleValue();
567                     calcPdl2 = impairments.get("PDL2").doubleValue();
568                     calcOnsrLin = impairments.get("ONSRLIN").doubleValue();
569                     if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
570                         return -1.0;
571                     }
572                     if (pathElement > 1) {
573                         // If SRG is not the first or the second element of the Path, it is the DROP
574                         // side.
575                         // After accumulated degradations are calculated, we also need to calculate
576                         // resulting OSNR in dB to pass it to the method that verifies end Xponder
577                         // performances are compatible with degradations experienced on the path
578                         try {
579                             calcOsnrdB = getOsnrDbfromOnsrLin(calcOnsrLin);
580                             LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOsnrdB);
581                             LOG.info("Loop n = {}, DROP, calcOsnrdB= {}", n, calcOsnrdB);
582                         } catch (ArithmeticException e) {
583                             LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
584                                 path.getEdgeList().size());
585                             return -1.0;
586                         }
587                     }
588                     if (CatalogConstant.CatalogNodeType.ADD.equals(cnt)) {
589                         // For the ADD, degradation brought by the node are calculated from the MW-WR spec.
590                         // The Degree is not considered. This means we must bypass the add-link (ADD)
591                         // and the next node (Degree) which are not considered in the impairments.
592                         n++;
593                     }
594                     impairments.clear();
595                     break;
596                 case DEGREE:
597                     if (nextNode.getORNodeType() != OpenroadmNodeType.DEGREE) {
598                         //This is the case of DROP, ROADM degree is not considered
599                         break;
600                     }
601                     LOG.info("loop of check OSNR : DEGREE, n = {} Path Element = {}", n, pathElement);
602                     cnt = CatalogConstant.CatalogNodeType.EXPRESS;
603                     String degree1Mode = "";
604                     String degree2Mode = "";
605 // If the operational mode of the Degree is not consistent or declared in the topology
606 // Operational mode is set by default to standard opMode for Degree
607                     degree1Mode = setOpMode(currentNode.getOperationalMode(), CatalogConstant.MWMWCORE);
608                     // Same for next node which is the second degree of a ROADM node
609                     degree2Mode = setOpMode(nextNode.getOperationalMode(), CatalogConstant.MWMWCORE);
610                     // At that time OpenROADM provides only one spec for the ROADM nodes
611                     if (!degree1Mode.equals(degree2Mode)) {
612                         LOG.info("Unsupported Hybrid ROADM configuration with Degree1 {} of {} operational mode"
613                             + "and Degree2 {} of {} operational mode. Will by default use operational mode"
614                             + "of Degree2", currentNode.getNodeId().toString(),
615                             degree1Mode, nextNode.getNodeId().toString(), degree2Mode);
616                     }
617                     pwrIn = pwrOut - getAppropriatePceLink((pathElement - offsetLink - 1), edges, allPceLinks,
618                         direction).getspanLoss();
619                     // Calculate degradation accumulated across incoming Link and add them to
620                     // accumulated impairments
621                     calcCd +=
622                         getAppropriatePceLink(pathElement - offsetLink - 1, edges, allPceLinks, direction).getcd();
623                     calcPmd2 +=
624                         getAppropriatePceLink(pathElement - offsetLink - 1, edges, allPceLinks, direction).getpmd2();
625                     // This also includes Non Linear Contribution from the path
626                     calcOnsrLin += cu.calculateNLonsrContribution(pwrOut,
627                         getAppropriatePceLink(pathElement - offsetLink - 1, edges, allPceLinks, direction).getLength(),
628                         spacing);
629                     // Calculate output power for next span (Output of degree 2)
630                     pwrOut = cu.getPceRoadmAmpOutputPower(cnt, degree2Mode,
631                         getAppropriatePceLink(pathElement + 3 * offsetLink + 1, edges, allPceLinks, direction)
632                             .getspanLoss(),
633                         spacing,
634                         getAppropriatePceLink(pathElement + 3 * offsetLink + 1, edges, allPceLinks, direction)
635                             .getpowerCorrection());
636                     // Adds to accumulated impairments the degradation associated with the Express
637                     // path of ROADM : Degree1, express link, Degree2
638                     impairments = cu.getPceRoadmAmpParameters(cnt, degree2Mode,
639                         pwrIn, calcCd, calcPmd2, calcPdl2, calcOnsrLin, spacing);
640                     calcCd = impairments.get("CD").doubleValue();
641                     calcPmd2 = impairments.get("DGD2").doubleValue();
642                     calcPdl2 = impairments.get("PDL2").doubleValue();
643                     calcOnsrLin = impairments.get("ONSRLIN").doubleValue();
644                     LOG.debug("Loop n = {}, DEGREE, calcOsnrdB= {}", n, getOsnrDbfromOnsrLin(calcOnsrLin));
645                     if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
646                         return -1.0;
647                     }
648                     // increment pathElement so that in next step we will not point to Degree2 but
649                     // next node
650                     n++;
651                     LOG.info("Accumulated degradations in the path including ROADM {} + {} are CD: {}; PMD2: "
652                         + "{}; Pdl2 : {}; ONSRdB : {}", currentNode.getNodeId().toString(),
653                         nextNode.getNodeId().toString(), calcCd, calcPmd2, calcPdl2, getOsnrDbfromOnsrLin(calcOnsrLin));
654                     break;
655                 default:
656                     LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain");
657             }
658         }
659         LOG.info("- In checkOSNR: accumulated CD = {} ps, PMD = {} ps, PDL = {} dB, and resulting OSNR calcOsnrdB = {} "
660             + "dB and ONSR dB exterapolated from calcosnrlin = {}"
661             + " including non linear contributions",
662             calcCd, Math.sqrt(calcPmd2), Math.sqrt(calcPdl2), calcOsnrdB, getOsnrDbfromOnsrLin(calcOnsrLin));
663         if (!transponderPresent) {
664             LOG.info("No transponder in the path, User shall check from CD, PMD, and OSNR values provided "
665                 + "that optical tunnel degradations are compatible with external transponder performances");
666             return 0.0;
667         }
668         double delta = margin - SYS_MARGIN;
669         LOG.info("In checkOSNR: Transponder Operational mode {} results in a residual margin of {} dB, according "
670             + "to CD, PMD and DGD induced penalties and set System Margin of {} dB.",
671             opMode, delta, SYS_MARGIN);
672         String validationMessage = delta >= 0 ? "VALIDATED" : "INVALIDATED";
673         if (direction.equals(StringConstants.SERVICE_DIRECTION_AZ)) {
674             LOG.info("- In checkOSNR: A to Z Path from {} to {} {}",
675                 vertices.get(0), vertices.get(vertices.size() - 1), validationMessage);
676         } else {
677             LOG.info("- In checkOSNR: Z to A Path from {} to {} {}",
678                 vertices.get(vertices.size() - 1), vertices.get(0), validationMessage);
679         }
680         return delta;
681     }
682
683     private String setOpMode(String opMode, String defaultMode) {
684         return
685             opMode == null || opMode.isEmpty() || opMode.contentEquals(StringConstants.UNKNOWN_MODE)
686                 ? defaultMode
687                 : opMode;
688     }
689
690
691     // Method to provide either regular link (AtoZ) or Opposite link (ZtoA) in the list of PceGraphEdges
692     private PceLink getAppropriatePceLink(Integer pathEltNber, List<PceGraphEdge> edges,
693             Map<LinkId, PceLink> allPceLinks, String direction) {
694         if ((StringConstants.SERVICE_DIRECTION_AZ).equals(direction)) {
695             // Returns regular link.
696             return edges.get(pathEltNber).link();
697         }
698             //For Z to A direction, must return the opposite link
699         return allPceLinks.get(new LinkId(edges.get(pathEltNber).link().getOppositeLink()));
700     }
701
702     private double getOsnrDbfromOnsrLin(double onsrLu) {
703         return (10 * Math.log10(1 / onsrLu));
704     }
705
706     /**
707      * Get spectrum assignment for path.
708      *
709      * @param path                    the path for which we get spectrum assignment.
710      * @param allPceNodes             all optical nodes.
711      * @param spectralWidthSlotNumber number of slot for spectral width. Depends on
712      *                                service type.
713      * @return a spectrum assignment object which contains begin and end index. If
714      *         no spectrum assignment found, beginIndex = stopIndex = 0
715      */
716     private SpectrumAssignment getSpectrumAssignment(GraphPath<String, PceGraphEdge> path,
717             Map<NodeId, PceNode> allPceNodes, int spectralWidthSlotNumber) {
718         byte[] freqMap = new byte[GridConstant.NB_OCTECTS];
719         Arrays.fill(freqMap, (byte) GridConstant.AVAILABLE_SLOT_VALUE);
720         BitSet result = BitSet.valueOf(freqMap);
721         boolean isFlexGrid = true;
722         LOG.debug("Processing path {} with length {}", path, path.getLength());
723         for (PceGraphEdge edge : path.getEdgeList()) {
724             NodeId srcNodeId = edge.link().getSourceId();
725             LOG.debug("Processing source {} ", srcNodeId);
726             if (allPceNodes.containsKey(srcNodeId)) {
727                 PceNode pceNode = allPceNodes.get(srcNodeId);
728                 LOG.debug("Processing PCE node {}", pceNode);
729                 String pceNodeVersion = pceNode.getVersion();
730                 NodeId pceNodeId = pceNode.getNodeId();
731                 BigDecimal sltWdthGran = pceNode.getSlotWidthGranularity();
732                 BigDecimal ctralFreqGran = pceNode.getCentralFreqGranularity();
733                 if (StringConstants.OPENROADM_DEVICE_VERSION_1_2_1.equals(pceNodeVersion)) {
734                     LOG.debug("Node {}: version is {} and slot width granularity is {} -> fixed grid mode",
735                         pceNodeId, pceNodeVersion, sltWdthGran);
736                     isFlexGrid = false;
737                 }
738                 if (sltWdthGran.setScale(0, RoundingMode.CEILING).equals(GridConstant.SLOT_WIDTH_50)
739                         && ctralFreqGran.setScale(0, RoundingMode.CEILING).equals(GridConstant.SLOT_WIDTH_50)) {
740                     LOG.debug("Node {}: version is {} with slot width granularity {} and central "
741                             + "frequency granularity is {} -> fixed grid mode",
742                         pceNodeId, pceNodeVersion, sltWdthGran, ctralFreqGran);
743                     isFlexGrid = false;
744                 }
745                 BitSet pceNodeFreqMap = pceNode.getBitSetData();
746                 LOG.debug("Pce node bitset {}", pceNodeFreqMap);
747                 if (pceNodeFreqMap != null) {
748                     result.and(pceNodeFreqMap);
749                     LOG.debug("intermediate bitset {}", result);
750                 }
751             }
752         }
753         LOG.debug("Bitset result {}", result);
754         return computeBestSpectrumAssignment(result, spectralWidthSlotNumber, isFlexGrid);
755     }
756
757     /**
758      * Compute spectrum assignment from spectrum occupation for spectral width.
759      *
760      * @param spectrumOccupation      the spectrum occupation BitSet.
761      * @param spectralWidthSlotNumber the nb slots for spectral width.
762      * @param isFlexGrid              true if flexible grid, false otherwise.
763      * @return a spectrum assignment object which contains begin and stop index. If
764      *         no spectrum assignment found, beginIndex = stopIndex = 0
765      */
766     private SpectrumAssignment computeBestSpectrumAssignment(
767             BitSet spectrumOccupation, int spectralWidthSlotNumber, boolean isFlexGrid) {
768         SpectrumAssignmentBuilder spectrumAssignmentBldr = new SpectrumAssignmentBuilder()
769             .setBeginIndex(Uint16.valueOf(0))
770             .setStopIndex(Uint16.valueOf(0))
771             .setFlexGrid(isFlexGrid);
772         BitSet referenceBitSet = new BitSet(spectralWidthSlotNumber);
773         referenceBitSet.set(0, spectralWidthSlotNumber);
774         //higher is the frequency, smallest is the wavelength number
775         //in operational, the allocation is done through wavelength starting from the smallest
776         //so we have to loop from the last element of the spectrum occupation
777         for (int i = spectrumOccupation.size(); i >= spectralWidthSlotNumber;
778                 i -= isFlexGrid ? spectralWidthSlotNumber : 1) {
779             if (spectrumOccupation.get(i - spectralWidthSlotNumber, i).equals(referenceBitSet)) {
780                 spectrumAssignmentBldr.setBeginIndex(Uint16.valueOf(i - spectralWidthSlotNumber));
781                 spectrumAssignmentBldr.setStopIndex(Uint16.valueOf(i - 1));
782                 break;
783             }
784         }
785         return spectrumAssignmentBldr.build();
786     }
787
788     public Double getTpceCalculatedMargin() {
789         return tpceCalculatedMargin;
790     }
791 }