32624da2faba0c5c669c79cdb08009ba814b52a4
[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.LinkedHashSet;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.concurrent.ExecutionException;
24 import java.util.stream.Collectors;
25 import org.jgrapht.GraphPath;
26 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
27 import org.opendaylight.transportpce.common.InstanceIdentifiers;
28 import org.opendaylight.transportpce.common.ResponseCodes;
29 import org.opendaylight.transportpce.common.StringConstants;
30 import org.opendaylight.transportpce.common.catalog.CatalogConstant;
31 import org.opendaylight.transportpce.common.catalog.CatalogConstant.CatalogNodeType;
32 import org.opendaylight.transportpce.common.catalog.CatalogUtils;
33 import org.opendaylight.transportpce.common.fixedflex.GridConstant;
34 import org.opendaylight.transportpce.common.fixedflex.GridUtils;
35 import org.opendaylight.transportpce.common.network.NetworkTransactionService;
36 import org.opendaylight.transportpce.pce.constraints.PceConstraints;
37 import org.opendaylight.transportpce.pce.constraints.PceConstraints.ResourcePair;
38 import org.opendaylight.transportpce.pce.networkanalyzer.PceLink;
39 import org.opendaylight.transportpce.pce.networkanalyzer.PceNode;
40 import org.opendaylight.transportpce.pce.networkanalyzer.PceResult;
41 import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev230925.PceConstraintMode;
42 import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev230925.SpectrumAssignment;
43 import org.opendaylight.yang.gen.v1.http.org.opendaylight.transportpce.pce.rev230925.SpectrumAssignmentBuilder;
44 import org.opendaylight.yang.gen.v1.http.org.openroadm.network.topology.rev230526.TerminationPoint1;
45 import org.opendaylight.yang.gen.v1.http.org.openroadm.network.types.rev230526.OpenroadmLinkType;
46 import org.opendaylight.yang.gen.v1.http.org.openroadm.network.types.rev230526.OpenroadmNodeType;
47 import org.opendaylight.yang.gen.v1.http.org.openroadm.otn.common.types.rev210924.OpucnTribSlotDef;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.network.rev180226.NodeId;
49 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.network.topology.rev180226.LinkId;
50 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
51 import org.opendaylight.yangtools.yang.common.Uint16;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 public class PostAlgoPathValidator {
56     /* Logging. */
57     private static final Logger LOG = LoggerFactory.getLogger(PostAlgoPathValidator.class);
58
59     public static final Long CONST_OSNR = 1L;
60     public static final double SYS_MARGIN = 0;
61
62     private Double tpceCalculatedMargin = 0.0;
63     private final NetworkTransactionService networkTransactionService;
64
65     public PostAlgoPathValidator(NetworkTransactionService networkTransactionService) {
66         this.networkTransactionService = networkTransactionService;
67     }
68
69     @SuppressWarnings("fallthrough")
70     @SuppressFBWarnings(
71         value = "SF_SWITCH_FALLTHROUGH",
72         justification = "intentional fallthrough")
73     public PceResult checkPath(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
74             Map<LinkId, PceLink> allPceLinks, PceResult pceResult, PceConstraints pceHardConstraints,
75             String serviceType, PceConstraintMode mode) {
76         LOG.info("path = {}", path);
77         // check if the path is empty
78         if (path.getEdgeList().isEmpty()) {
79             pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
80             return pceResult;
81         }
82         int spectralWidthSlotNumber =
83             GridConstant.SPECTRAL_WIDTH_SLOT_NUMBER_MAP.getOrDefault(serviceType, GridConstant.NB_SLOTS_100G);
84         SpectrumAssignment spectrumAssignment = null;
85         //variable to deal with 1GE (Nb=1) and 10GE (Nb=10) cases
86         switch (serviceType) {
87             case StringConstants.SERVICE_TYPE_OTUC2:
88             case StringConstants.SERVICE_TYPE_OTUC3:
89             case StringConstants.SERVICE_TYPE_OTUC4:
90             case StringConstants.SERVICE_TYPE_400GE:
91                 spectralWidthSlotNumber =
92                     GridConstant.SPECTRAL_WIDTH_SLOT_NUMBER_MAP.getOrDefault(serviceType, GridConstant.NB_SLOTS_400G);
93             //fallthrough
94             case StringConstants.SERVICE_TYPE_100GE_T:
95             case StringConstants.SERVICE_TYPE_OTU4:
96                 spectrumAssignment = getSpectrumAssignment(path, allPceNodes, spectralWidthSlotNumber);
97                 pceResult.setServiceType(serviceType);
98                 if (spectrumAssignment.getBeginIndex().equals(Uint16.valueOf(0))
99                         && spectrumAssignment.getStopIndex().equals(Uint16.valueOf(0))) {
100                     pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
101                     pceResult.setLocalCause(PceResult.LocalCause.NO_PATH_EXISTS);
102                     return pceResult;
103                 }
104                 if (spectrumAssignment.getFlexGrid()) {
105                     LOG.debug("Spectrum assignment flexgrid mode");
106                     pceResult.setResultWavelength(GridConstant.IRRELEVANT_WAVELENGTH_NUMBER);
107                 } else {
108                     LOG.debug("Spectrum assignment fixedgrid mode");
109                     pceResult.setResultWavelength(
110                         GridUtils.getWaveLengthIndexFromSpectrumAssigment(spectrumAssignment.getBeginIndex().toJava()));
111                 }
112                 pceResult.setMinFreq(GridUtils.getStartFrequencyFromIndex(spectrumAssignment.getBeginIndex().toJava()));
113                 pceResult.setMaxFreq(GridUtils.getStopFrequencyFromIndex(spectrumAssignment.getStopIndex().toJava()));
114                 LOG.debug("In PostAlgoPathValidator: spectrum assignment found {} {}", spectrumAssignment, path);
115
116                 // Check the OSNR
117                 CatalogUtils cu = new CatalogUtils(networkTransactionService);
118                 if (cu.isCatalogFilled()) {
119                     double margin1 = checkOSNR(path, allPceNodes, allPceLinks, serviceType,
120                             StringConstants.SERVICE_DIRECTION_AZ, cu);
121                     double margin2 = checkOSNR(path, allPceNodes, allPceLinks, serviceType,
122                             StringConstants.SERVICE_DIRECTION_ZA, cu);
123                     if (margin1 < 0 || margin2 < 0 || margin1 == Double.NEGATIVE_INFINITY
124                             || margin2 == Double.NEGATIVE_INFINITY) {
125                         pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
126                         pceResult.setLocalCause(PceResult.LocalCause.OUT_OF_SPEC_OSNR);
127                         return pceResult;
128                     }
129                     this.tpceCalculatedMargin = Math.min(margin1, margin2);
130                     LOG.info(
131                         "In PostAlgoPathValidator: Minimum margin estimated by tpce on AtoZ and ZtoA path is of  {} dB",
132                         this.tpceCalculatedMargin);
133                 } else {
134                     this.tpceCalculatedMargin = 0.0;
135                     LOG.info("In PostAlgoPathValidator: Operational mode Catalog not filled, delegate OSNR calculation"
136                         + " to GNPy and margin set to 0");
137                 }
138                 // Check if MaxLatency is defined in the hard constraints
139                 if (pceHardConstraints.getMaxLatency() != -1
140                         && !checkLatency(pceHardConstraints.getMaxLatency(), path)) {
141                     pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
142                     pceResult.setLocalCause(PceResult.LocalCause.TOO_HIGH_LATENCY);
143                     return pceResult;
144                 }
145                 // Check if nodes are included in the hard constraints
146                 if (!checkInclude(path, pceHardConstraints, mode)) {
147                     pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
148                     pceResult.setLocalCause(PceResult.LocalCause.HD_NODE_INCLUDE);
149                     return pceResult;
150                 }
151                 // TODO here other post algo validations can be added
152                 // more data can be sent to PceGraph module via PceResult structure if required
153                 pceResult.setRC(ResponseCodes.RESPONSE_OK);
154                 pceResult.setLocalCause(PceResult.LocalCause.NONE);
155                 return pceResult;
156             case StringConstants.SERVICE_TYPE_100GE_M:
157             case StringConstants.SERVICE_TYPE_10GE:
158             case StringConstants.SERVICE_TYPE_1GE:
159                 int tribSlotNb = Map.of(
160                         StringConstants.SERVICE_TYPE_100GE_M, 20,
161                         StringConstants.SERVICE_TYPE_10GE, 8,
162                         StringConstants.SERVICE_TYPE_1GE, 1)
163                     .get(serviceType);
164                 pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
165                 pceResult.setServiceType(serviceType);
166                 Map<String, List<Uint16>> tribSlot = chooseTribSlot(path, allPceNodes, tribSlotNb);
167                 Map<String, Uint16> tribPort = chooseTribPort(path, allPceNodes, tribSlot, tribSlotNb);
168                 List<OpucnTribSlotDef> resultTribPortTribSlot = getMinMaxTpTs(tribPort, tribSlot);
169                 if (resultTribPortTribSlot.get(0) != null && resultTribPortTribSlot.get(1) != null) {
170                     pceResult.setResultTribPortTribSlot(resultTribPortTribSlot);
171                     pceResult.setRC(ResponseCodes.RESPONSE_OK);
172                     LOG.info("In PostAlgoPathValidator: found TribPort {} - tribSlot {} - tribSlotNb {}",
173                         tribPort, tribSlot, tribSlotNb);
174                 }
175                 return pceResult;
176             case StringConstants.SERVICE_TYPE_ODU4:
177             case StringConstants.SERVICE_TYPE_ODUC2:
178             case StringConstants.SERVICE_TYPE_ODUC3:
179             case StringConstants.SERVICE_TYPE_ODUC4:
180             case StringConstants.SERVICE_TYPE_100GE_S:
181                 pceResult.setRC(ResponseCodes.RESPONSE_OK);
182                 pceResult.setServiceType(serviceType);
183                 LOG.info("In PostAlgoPathValidator: ODU4/ODUCn path found {}", path);
184                 return pceResult;
185             default:
186                 pceResult.setRC(ResponseCodes.RESPONSE_FAILED);
187                 LOG.warn("In PostAlgoPathValidator checkPath: unsupported serviceType {} found {}",
188                     serviceType, path);
189                 return pceResult;
190         }
191     }
192
193     // Check the latency
194     private boolean checkLatency(Long maxLatency, GraphPath<String, PceGraphEdge> path) {
195         double latency = 0;
196         for (PceGraphEdge edge : path.getEdgeList()) {
197             if (edge.link() == null || edge.link().getLatency() == null) {
198                 LOG.warn("- In checkLatency: the link {} does not contain latency field",
199                     edge.link().getLinkId().getValue());
200                 return false;
201             }
202             latency += edge.link().getLatency();
203             LOG.debug("- In checkLatency: latency of {} = {} units", edge.link().getLinkId().getValue(), latency);
204         }
205         return (latency < maxLatency);
206     }
207
208     // Check the inclusion if it is defined in the hard constraints
209     //TODO: remove this checkstyle false positive warning when the checkstyle bug will be fixed
210     @SuppressWarnings("MissingSwitchDefault")
211     private boolean checkInclude(GraphPath<String, PceGraphEdge> path, PceConstraints pceHardConstraintsInput,
212             PceConstraintMode mode) {
213         List<ResourcePair> listToInclude = pceHardConstraintsInput.getListToInclude();
214         if (listToInclude.isEmpty()) {
215             return true;
216         }
217         List<PceGraphEdge> pathEdges = path.getEdgeList();
218         LOG.debug(" in checkInclude vertex list: [{}]", path.getVertexList());
219         LOG.debug("listToInclude = {}", listToInclude);
220         List<String> listOfElementsSubNode = new ArrayList<>();
221         listOfElementsSubNode.add(pathEdges.get(0).link().getsourceNetworkSupNodeId());
222         listOfElementsSubNode.addAll(
223             listOfElementsBuild(pathEdges, PceConstraints.ResourceType.NODE, pceHardConstraintsInput));
224         List<String> listOfElementsCLLI = new ArrayList<>();
225         listOfElementsCLLI.add(pathEdges.get(0).link().getsourceCLLI());
226         listOfElementsCLLI.addAll(
227             listOfElementsBuild(pathEdges, PceConstraints.ResourceType.CLLI, pceHardConstraintsInput));
228         List<String> listOfElementsSRLG = new ArrayList<>();
229         // first link is XPONDEROUTPUT, no SRLG for it
230         listOfElementsSRLG.add("NONE");
231         listOfElementsSRLG.addAll(
232             listOfElementsBuild(pathEdges, PceConstraints.ResourceType.SRLG, pceHardConstraintsInput));
233         // validation: check each type for each element
234         LOG.debug("listOfElementsSubNode = {}", listOfElementsSubNode);
235         return switch (mode) {
236             case Loose -> listOfElementsSubNode
237                 .containsAll(listToInclude.stream()
238                     .filter(rp -> PceConstraints.ResourceType.NODE.equals(rp.getType()))
239                     .map(ResourcePair::getName).collect(Collectors.toList()));
240             case Strict -> listOfElementsSubNode
241                 .equals(listToInclude.stream()
242                     .filter(rp -> PceConstraints.ResourceType.NODE.equals(rp.getType()))
243                     .map(ResourcePair::getName).collect(Collectors.toList()));
244         }
245             && listOfElementsSRLG.containsAll(
246                 listToInclude
247                     .stream().filter(rp -> PceConstraints.ResourceType.SRLG.equals(rp.getType()))
248                     .map(ResourcePair::getName).collect(Collectors.toList()))
249             && listOfElementsCLLI.containsAll(
250                 listToInclude
251                     .stream().filter(rp -> PceConstraints.ResourceType.CLLI.equals(rp.getType()))
252                     .map(ResourcePair::getName).collect(Collectors.toList()));
253     }
254
255     private List<String> listOfElementsBuild(List<PceGraphEdge> pathEdges, PceConstraints.ResourceType type,
256             PceConstraints pceHardConstraints) {
257         Set<String> listOfElements = new LinkedHashSet<>();
258         for (PceGraphEdge link : pathEdges) {
259             switch (type) {
260                 case NODE:
261                     listOfElements.add(link.link().getdestNetworkSupNodeId());
262                     break;
263                 case CLLI:
264                     listOfElements.add(link.link().getdestCLLI());
265                     break;
266                 case SRLG:
267                     if (link.link().getlinkType() != OpenroadmLinkType.ROADMTOROADM) {
268                         listOfElements.add("NONE");
269                         break;
270                     }
271                     // srlg of link is List<Long>. But in this algo we need string representation of
272                     // one SRLG
273                     // this should be any SRLG mentioned in include constraints if any of them if
274                     // mentioned
275                     boolean found = false;
276                     for (Long srlg : link.link().getsrlgList()) {
277                         String srlgStr = String.valueOf(srlg);
278                         if (pceHardConstraints.getSRLGnames().contains(srlgStr)) {
279                             listOfElements.add(srlgStr);
280                             LOG.info("listOfElementsBuild. FOUND SRLG {} in link {}", srlgStr, link.link());
281                             found = true;
282                         }
283                     }
284                     if (!found) {
285                         // there is no specific srlg to include. thus add to list just the first one
286                         listOfElements.add("NONE");
287                     }
288                     break;
289                 default:
290                     LOG.debug("listOfElementsBuild unsupported resource type");
291             }
292         }
293         return new ArrayList<>(listOfElements);
294     }
295
296     private Map<String, Uint16> chooseTribPort(GraphPath<String,
297             PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes, Map<String, List<Uint16>> tribSlotMap, int nbSlot) {
298         LOG.debug("In choosetribPort: edgeList = {} ", path.getEdgeList());
299         Map<String, Uint16> tribPortMap = new HashMap<>();
300         for (PceGraphEdge edge : path.getEdgeList()) {
301             List<Uint16> srcTpnPool =
302                 allPceNodes
303                     .get(edge.link().getSourceId())
304                     .getAvailableTribPorts()
305                     .get(edge.link().getSourceTP().getValue());
306             List<Uint16> destTpnPool =
307                 allPceNodes
308                     .get(edge.link().getDestId())
309                     .getAvailableTribPorts()
310                     .get(edge.link().getDestTP().getValue());
311             List<Uint16> commonEdgeTpnPool = new ArrayList<>();
312             for (Uint16 srcTpn : srcTpnPool) {
313                 if (destTpnPool.contains(srcTpn)) {
314                     commonEdgeTpnPool.add(srcTpn);
315                 }
316             }
317             if (commonEdgeTpnPool.isEmpty()) {
318                 continue;
319             }
320             Integer startTribSlot = tribSlotMap.values().stream().findFirst().orElseThrow().get(0).toJava();
321             Integer tribPort = (int) Math.ceil((double)startTribSlot / nbSlot);
322             for (Uint16 commonTribPort : commonEdgeTpnPool) {
323                 if (tribPort.equals(commonTribPort.toJava())) {
324                     tribPortMap.put(edge.link().getLinkId().getValue(), commonTribPort);
325                 }
326             }
327         }
328         tribPortMap.forEach((k,v) -> LOG.info("TribPortMap : k = {}, v = {}", k, v));
329         return tribPortMap;
330     }
331
332     private Map<String, List<Uint16>> chooseTribSlot(GraphPath<String,
333             PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes, int nbSlot) {
334         LOG.debug("In choosetribSlot: edgeList = {} ", path.getEdgeList());
335         Map<String, List<Uint16>> tribSlotMap = new HashMap<>();
336         for (PceGraphEdge edge : path.getEdgeList()) {
337             List<Uint16> srcTsPool =
338                 allPceNodes
339                     .get(edge.link().getSourceId())
340                     .getAvailableTribSlots()
341                     .get(edge.link().getSourceTP().getValue());
342             List<Uint16> destTsPool =
343                 allPceNodes
344                     .get(edge.link().getDestId())
345                     .getAvailableTribSlots()
346                     .get(edge.link().getDestTP().getValue());
347             List<Uint16> commonEdgeTsPoolList = new ArrayList<>();
348             List<Uint16> tribSlotList = new ArrayList<>();
349             for (Uint16 integer : srcTsPool) {
350                 if (destTsPool.contains(integer)) {
351                     commonEdgeTsPoolList.add(integer);
352                 }
353             }
354             Collections.sort(commonEdgeTsPoolList);
355             List<Uint16> commonGoodStartEdgeTsPoolList = new ArrayList<>();
356             for (Uint16 startEdgeTsPool : commonEdgeTsPoolList) {
357                 if (Integer.valueOf(1).equals(startEdgeTsPool.toJava() % nbSlot) || nbSlot == 1) {
358                     commonGoodStartEdgeTsPoolList.add(startEdgeTsPool);
359                 }
360             }
361             Collections.sort(commonGoodStartEdgeTsPoolList);
362             boolean goodTsList = false;
363             for (Uint16 goodStartTsPool : commonGoodStartEdgeTsPoolList) {
364                 int goodStartIndex = commonEdgeTsPoolList.indexOf(Uint16.valueOf(goodStartTsPool.intValue()));
365                 if (!goodTsList && commonEdgeTsPoolList.size() - goodStartIndex >= nbSlot) {
366                     for (int i = 0; i < nbSlot; i++) {
367                         if (!commonEdgeTsPoolList.get(goodStartIndex + i)
368                                 .equals(Uint16.valueOf(goodStartTsPool.toJava() + i))) {
369                             goodTsList = false;
370                             tribSlotList.clear();
371                             break;
372                         }
373                         tribSlotList.add(commonEdgeTsPoolList.get(goodStartIndex + i));
374                         goodTsList = true;
375                     }
376                 }
377             }
378             tribSlotMap.put(edge.link().getLinkId().getValue(), tribSlotList);
379         }
380         tribSlotMap.forEach((k,v) -> LOG.info("TribSlotMap : k = {}, v = {}", k, v));
381         return tribSlotMap;
382     }
383
384     private List<OpucnTribSlotDef> getMinMaxTpTs(Map<String, Uint16> tribPort, Map<String, List<Uint16>> tribSlot) {
385         String tribport = tribPort.values().toArray()[0].toString();
386         @SuppressWarnings("unchecked") List<Uint16> tsList = (List<Uint16>) tribSlot.values().toArray()[0];
387         return new ArrayList<>(List.of(
388             OpucnTribSlotDef.getDefaultInstance(String.join(".", tribport, tsList.get(0).toString())),
389             OpucnTribSlotDef.getDefaultInstance(String.join(".", tribport, tsList.get(tsList.size() - 1).toString()))));
390     }
391
392     private double checkOSNR(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
393             Map<LinkId, PceLink> allPceLinks, String serviceType, String direction, CatalogUtils cu) {
394         switch (direction) {
395             case StringConstants.SERVICE_DIRECTION_AZ:
396                 return checkOSNRaz(path, allPceNodes, allPceLinks, serviceType, cu);
397             case StringConstants.SERVICE_DIRECTION_ZA:
398                 return checkOSNRza(path, allPceNodes, allPceLinks, serviceType, cu);
399             default:
400                 LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported direction {}", direction);
401                 return 0.0;
402         }
403     }
404
405     /**
406      * Calculates the OSNR of a path, according to the direction (AtoZ/ZtoA), using the operational-modes Catalog.
407      *
408      * @param path                      the AtoZ path provided by the PCE.
409      * @param allPceNode                The map of chosen/relevant PceNodes build from topology pruning.
410      * @param allPceLinks               The map of PceLinks build corresponding to the whole topology.
411      * @param serviceType               The service Type used to extrapolate Operational mode when it is not provided.
412      * @param cu                        CatalogUtils instance.
413      * @return the calculated margin according to the Transponder performances and path impairments.
414      */
415     private double checkOSNRaz(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
416             Map<LinkId, PceLink> allPceLinks, String serviceType, CatalogUtils cu) {
417         Map<String, Double> signal = new HashMap<>(
418             Map.of(
419                 "spacing", Double.valueOf(50.0),
420                 "calcPdl2", Double.valueOf(0),
421                 "calcCd", Double.valueOf(0),
422                 "calcPmd2", Double.valueOf(0),
423                 "calcOnsrLin", Double.valueOf(0.0001),
424                 "pwrIn", Double.valueOf(-60.0),
425                 "pwrOut", Double.valueOf(-60.0)));
426         double calcOnsrdB = 0;
427         double margin = 0;
428         boolean transponderPresent = false;
429         List<String> vertices = path.getVertexList();
430         List<PceGraphEdge> edges = path.getEdgeList();
431         // LOOP that scans the different Nodes/Links of the path and calculates
432         // associated degradations
433         // using CatalogUtils primitives to retrieve physical parameters and make a
434         // first level calculation
435         int bypassDegree = 0;
436         for (int pathElement = 0; pathElement < 2; pathElement++) {
437             bypassDegree = 0;
438             PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(pathElement)));
439             PceNode nextNode = allPceNodes.get(new NodeId(vertices.get(pathElement + 1)));
440             LOG.debug("loop of check OSNR direction AZ, Path Element = {}", pathElement);
441             switch (currentNode.getORNodeType()) {
442                 case XPONDER:
443                     LOG.debug("loop of check OSNR direction AZ: XPDR, Path Element = {}", pathElement);
444                     transponderPresent = true;
445                     calcXpdrOSNR(cu, signal,
446                         pathElement == 0
447                             // First transponder on the Path (TX side) / Last Xponder of the path (RX side)
448                             ? edges.get(pathElement).link().getSourceTP().getValue()
449                             : edges.get(pathElement - 1).link().getDestTP().getValue(),
450                         serviceType, currentNode, nextNode, vertices.get(pathElement), pathElement);
451                     break;
452                 case SRG:
453                     LOG.debug("loop of check OSNR direction AZ: SRG, Path Element = {}", pathElement);
454                     // This is ADD case : First (optical-tunnel) or 2nd (Regular E2E service from
455                     // Xponder to Xponder) node element of the path is the ADD SRG.
456                     if (edges.get(pathElement).link().getlinkType() != OpenroadmLinkType.ADDLINK) {
457                         LOG.error("Error processing Node {} for which output link {} is not an ADDLINK Type",
458                             currentNode.getNodeId(), pathElement);
459                     }
460                     signal.put("pwrIn", Double.valueOf(0));
461                     calcAddContrib(cu, signal, currentNode, edges.get(pathElement + 1).link());
462                     LOG.debug("loop of check OSNR direction AZ: SRG, pathElement = {} link {} Pout = {}",
463                         pathElement, pathElement + 1, signal.get("pwrOut"));
464                     double calcOnsr = signal.get("calcOnsrLin").doubleValue();
465                     if (calcOnsr == Double.NEGATIVE_INFINITY || calcOnsr == Double.POSITIVE_INFINITY) {
466                         return -1.0;
467                     }
468                     // For the ADD, degradation brought by the node are calculated from the MW-WR spec.
469                     // The Degree is not considered. This means we must bypass the add-link (ADD)
470                     // and the next node (Degree) which are not considered in the impairments.
471                     pathElement++;
472                     bypassDegree = 1;
473                     break;
474                 case DEGREE:
475                 default:
476                     LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain");
477             }
478         }
479         for (int pathElement = 2 + bypassDegree; pathElement < vertices.size() - 1; pathElement++) {
480             PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(pathElement)));
481             PceNode nextNode = allPceNodes.get(new NodeId(vertices.get(pathElement + 1)));
482             LOG.debug("loop of check OSNR direction AZ: Path Element = {}", pathElement);
483             switch (currentNode.getORNodeType()) {
484                 case SRG:
485                     LOG.debug("loop of check OSNR direction AZ: SRG, Path Element = {}", pathElement);
486                     // Other case is DROP, for which cnt is unchanged (.DROP)
487                     if (edges.get(pathElement - 1).link().getlinkType() != OpenroadmLinkType.DROPLINK) {
488                         LOG.error("Error processing Node {} for which input link {} is not a DROPLINK Type",
489                             currentNode.getNodeId(), pathElement - 1);
490                     }
491                     PceLink pceLink = edges.get(pathElement - 2).link();
492                     LOG.info("loop of check OSNR : SRG, pathElement = {} CD on preceeding link {} = {} ps",
493                         pathElement, pathElement - 2, pceLink.getcd());
494                     calcDropContrib(cu, signal, currentNode, pceLink);
495                     double calcOnsr = signal.get("calcOnsrLin").doubleValue();
496                     if (calcOnsr == Double.NEGATIVE_INFINITY || calcOnsr == Double.POSITIVE_INFINITY) {
497                         return -1.0;
498                     }
499                     // If SRG is not the first or the second element of the Path, it is the DROP
500                     // side.
501                     // After accumulated degradations are calculated, we also need to calculate
502                     // resulting OSNR in dB to pass it to the method that verifies end Xponder
503                     // performances are compatible with degradations experienced on the path
504                     try {
505                         calcOnsrdB = getOsnrDbfromOnsrLin(calcOnsr);
506                         LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOnsrdB);
507                         LOG.info("Loop pathElement = {}, DROP, calcOnsrdB= {}", pathElement, calcOnsrdB);
508                     } catch (ArithmeticException e) {
509                         LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
510                             path.getEdgeList().size());
511                         return -1.0;
512                     }
513                     break;
514                 case DEGREE:
515                     if (nextNode.getORNodeType() != OpenroadmNodeType.DEGREE) {
516                         //This is the case of DROP, ROADM degree is not considered
517                         break;
518                     }
519                     LOG.info("loop of check OSNR direction AZ: DEGREE, Path Element = {}", pathElement);
520                     calcBypassContrib(cu, signal, currentNode, nextNode,
521                         edges.get(pathElement - 1).link(), edges.get(pathElement + 1).link());
522                     double calcOnsrLin = signal.get("calcOnsrLin").doubleValue();
523                     LOG.debug(
524                         "Loop pathElement= {}, DEGREE, calcOnsrdB= {}", pathElement, getOsnrDbfromOnsrLin(calcOnsrLin));
525                     if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
526                         return -1.0;
527                     }
528                     // increment pathElement so that in next step we will not point to Degree2 but
529                     // next node
530                     pathElement++;
531                     LOG.info("Accumulated degradations in the path including ROADM {} + {} are CD: {}; PMD2: "
532                         + "{}; Pdl2 : {}; ONSRdB : {}", currentNode.getNodeId(), nextNode.getNodeId(),
533                         signal.get("calcCd"), signal.get("calcPmd2"), signal.get("calcPdl2"),
534                         getOsnrDbfromOnsrLin(calcOnsrLin));
535                     break;
536                 case XPONDER:
537                     LOG.debug("loop of check OSNR direction AZ: XPDR, Path Element = {}", pathElement);
538                     LOG.error("unsupported back to back transponder configuration");
539                     return -1.0;
540                 default:
541                     LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain");
542             }
543         }
544         PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(vertices.size() - 1)));
545         LOG.debug("loop of check OSNR, Path Element = {}", vertices.size() - 1);
546         switch (currentNode.getORNodeType()) {
547             case XPONDER:
548                 LOG.debug("loop of check OSNR direction AZ: XPDR, Path Element = {}", vertices.size() - 1);
549                 transponderPresent = true;
550                 // TSP is the last of the path
551                 margin = getLastXpdrMargin(cu, signal, edges.get(vertices.size() - 2).link().getDestTP().getValue(),
552                     serviceType, currentNode, vertices.get(vertices.size() - 1), vertices.size() - 1);
553                 break;
554             case SRG:
555                 LOG.debug("loop of check OSNR direction AZ: SRG, Path Element = {}", vertices.size() - 1);
556                 // Other case is DROP, for which cnt is unchanged (.DROP)
557                 if (edges.get(vertices.size() - 2).link().getlinkType() != OpenroadmLinkType.DROPLINK) {
558                     LOG.error("Error processing Node {} for which input link {} is not a DROPLINK Type",
559                         currentNode.getNodeId(), vertices.size() - 2);
560                 }
561                 PceLink pceLink = edges.get(vertices.size() - 3).link();
562                 LOG.info("loop of check OSNR : SRG, pathElement = {} CD on preceeding link {} = {} ps",
563                     vertices.size() - 1, vertices.size() - 3, pceLink.getcd());
564                 calcDropContrib(cu, signal, currentNode, pceLink);
565                 double calcOnsr = signal.get("calcOnsrLin").doubleValue();
566                 //commented out to avoid spotbug DLS_DEAD_LOCAL_STORE pwrIn = impairments.get("pwrIn");
567                 if (calcOnsr == Double.NEGATIVE_INFINITY || calcOnsr == Double.POSITIVE_INFINITY) {
568                     return -1.0;
569                 }
570                 // If SRG is not the first or the second element of the Path, it is the DROP
571                 // side.
572                 // After accumulated degradations are calculated, we also need to calculate
573                 // resulting OSNR in dB to pass it to the method that verifies end Xponder
574                 // performances are compatible with degradations experienced on the path
575                 try {
576                     calcOnsrdB = getOsnrDbfromOnsrLin(calcOnsr);
577                     LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOnsrdB);
578                     LOG.info("Loop pathElement = {}, DROP, calcOnsrdB= {}", vertices.size() - 1, calcOnsrdB);
579                 } catch (ArithmeticException e) {
580                     LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
581                         path.getEdgeList().size());
582                     return -1.0;
583                 }
584                 break;
585             case DEGREE:
586             default:
587                 LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain last element");
588         }
589         LOG.info("- In checkOSNR: accumulated CD = {} ps, PMD = {} ps, PDL = {} dB, and resulting OSNR calcOnsrdB = {} "
590             + "dB and ONSR dB exterapolated from calcosnrlin = {} including non linear contributions",
591             signal.get("calcCd"), Math.sqrt(signal.get("calcPmd2").doubleValue()),
592             Math.sqrt(signal.get("calcPdl2").doubleValue()), calcOnsrdB,
593             getOsnrDbfromOnsrLin(signal.get("calcOnsrLin").doubleValue()));
594         if (!transponderPresent) {
595             LOG.info("No transponder in the path, User shall check from CD, PMD, and OSNR values provided "
596                 + "that optical tunnel degradations are compatible with external transponder performances");
597             return 0.0;
598         }
599         double delta = margin - SYS_MARGIN;
600         LOG.info("In checkOSNR: Transponder Operational mode results in a residual margin of {} dB, according "
601             + "to CD, PMD and DGD induced penalties and set System Margin of {} dB.",
602             delta, SYS_MARGIN);
603         String validationMessage = delta >= 0 ? "VALIDATED" : "INVALIDATED";
604         LOG.info("- In checkOSNR: A to Z Path from {} to {} {}",
605                 vertices.get(0), vertices.get(vertices.size() - 1), validationMessage);
606         return delta;
607     }
608
609     /**
610      * Calculates the OSNR of a path, according to the direction (AtoZ/ZtoA), using the operational-modes Catalog.
611      *
612      * @param path                      the AtoZ path provided by the PCE.
613      * @param allPceNode                The map of chosen/relevant PceNodes build from topology pruning.
614      * @param allPceLinks               The map of PceLinks build corresponding to the whole topology.
615      * @param serviceType               The service Type used to extrapolate Operational mode when it is not provided.
616      * @param cu                        CatalogUtils instance.
617      * @return the calculated margin according to the Transponder performances and path impairments.
618      */
619     private double checkOSNRza(GraphPath<String, PceGraphEdge> path, Map<NodeId, PceNode> allPceNodes,
620             Map<LinkId, PceLink> allPceLinks, String serviceType, CatalogUtils cu) {
621         Map<String, Double> signal = new HashMap<>(
622             Map.of(
623                 "spacing", Double.valueOf(50.0),
624                 "calcPdl2", Double.valueOf(0),
625                 "calcCd", Double.valueOf(0),
626                 "calcPmd2", Double.valueOf(0),
627                 "calcOnsrLin", Double.valueOf(0.0001),
628                 "pwrIn", Double.valueOf(-60.0),
629                 "pwrOut", Double.valueOf(-60.0)));
630         double calcOnsrdB = 0;
631         double margin = 0;
632         boolean transponderPresent = false;
633         List<String> vertices = path.getVertexList();
634         List<PceGraphEdge> edges = path.getEdgeList();
635         // LOOP that scans the different Nodes/Links of the path and calculates
636         // associated degradations
637         // using CatalogUtils primitives to retrieve physical parameters and make a
638         // first level calculation
639         int bypassDegree = 0;
640         for (int pathElement = vertices.size() - 1; pathElement > vertices.size() - 3; pathElement--) {
641             bypassDegree = 0;
642             PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(pathElement)));
643             PceNode nextNode = allPceNodes.get(new NodeId(vertices.get(pathElement - 1)));
644             LOG.debug("loop of check OSNR direction ZA:  Path Element = {}", pathElement);
645             switch (currentNode.getORNodeType()) {
646                 case XPONDER:
647                     LOG.debug("loop of check OSNR direction ZA: XPDR, Path Element = {}", pathElement);
648                     transponderPresent = true;
649                     calcXpdrOSNR(cu, signal,
650                         pathElement == vertices.size() - 1
651                             // First transponder on the Path (TX side) / Last Xponder of the path (RX side)
652                             ? getOppPceLink(pathElement - 1, edges, allPceLinks).getSourceTP().getValue()
653                             : getOppPceLink((pathElement), edges, allPceLinks).getDestTP().getValue(),
654                         serviceType, currentNode, nextNode, vertices.get(pathElement), pathElement);
655                     break;
656                 case SRG:
657                     LOG.debug("loop of check OSNR direction ZA: SRG, Path Element = {}", pathElement);
658                     // This is ADD case : First (optical-tunnel) or 2nd (Regular E2E service from
659                     // Xponder to Xponder) node element of the path is the ADD SRG.
660                     if (getOppPceLink(pathElement - 1, edges, allPceLinks).getlinkType() != OpenroadmLinkType.ADDLINK) {
661                         LOG.error("Error processing Node {} for which output link {} is not an ADDLINK Type",
662                             currentNode.getNodeId(), pathElement - 1);
663                     }
664                     signal.put("pwrIn", Double.valueOf(0));
665                     calcAddContrib(cu, signal, currentNode, getOppPceLink(pathElement - 2, edges, allPceLinks));
666                     double calcOnsr = signal.get("calcOnsrLin").doubleValue();
667                     if (calcOnsr == Double.NEGATIVE_INFINITY || calcOnsr == Double.POSITIVE_INFINITY) {
668                         return -1.0;
669                     }
670                     // For the ADD, degradation brought by the node are calculated from the MW-WR spec.
671                     // The Degree is not considered. This means we must bypass the add-link (ADD)
672                     // and the next node (Degree) which are not considered in the impairments.
673                     pathElement--;
674                     bypassDegree = 1;
675                     break;
676                 case DEGREE:
677                 default:
678                     LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain");
679             }
680         }
681         for (int pathElement = vertices.size() - 3 - bypassDegree; pathElement > 0; pathElement--) {
682             PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(pathElement)));
683             PceNode nextNode = allPceNodes.get(new NodeId(vertices.get(pathElement - 1)));
684             LOG.debug("loop of check OSNR direction ZA: Path Element = {}", pathElement);
685             switch (currentNode.getORNodeType()) {
686                 case SRG:
687                     LOG.debug("loop of check OSNR direction ZA: SRG, Path Element = {}", pathElement);
688                     if (getOppPceLink(pathElement, edges, allPceLinks).getlinkType() != OpenroadmLinkType.DROPLINK) {
689                         LOG.error("Error processing Node {} for which input link {} is not a DROPLINK Type",
690                             currentNode.getNodeId(), pathElement);
691                     }
692                     PceLink pceLink = getOppPceLink(pathElement + 1, edges, allPceLinks);
693                     LOG.info("loop of check OSNR direction ZA: SRG, path Element = {} CD on preceeding link {} = {} ps",
694                         pathElement, pathElement + 1, pceLink.getcd());
695                     calcDropContrib(cu, signal, currentNode, pceLink);
696                     double calcOnsr = signal.get("calcOnsrLin").doubleValue();
697                     if (calcOnsr == Double.NEGATIVE_INFINITY || calcOnsr == Double.POSITIVE_INFINITY) {
698                         return -1.0;
699                     }
700                     // If SRG is not the first or the second element of the Path, it is the DROP
701                     // side.
702                     // After accumulated degradations are calculated, we also need to calculate
703                     // resulting OSNR in dB to pass it to the method that verifies end Xponder
704                     // performances are compatible with degradations experienced on the path
705                     try {
706                         calcOnsrdB = getOsnrDbfromOnsrLin(calcOnsr);
707                         LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOnsrdB);
708                         LOG.info("Loop Path Element = {}, DROP, calcOnsrdB= {}", pathElement, calcOnsrdB);
709                     } catch (ArithmeticException e) {
710                         LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
711                             path.getEdgeList().size());
712                         return -1.0;
713                     }
714                     break;
715                 case DEGREE:
716                     if (nextNode.getORNodeType() != OpenroadmNodeType.DEGREE) {
717                         //This is the case of DROP, ROADM degree is not considered
718                         break;
719                     }
720                     LOG.info("loop of check OSNR direction ZA: DEGREE, Path Element = {}", pathElement);
721                     calcBypassContrib(cu, signal, currentNode, nextNode,
722                         getOppPceLink(pathElement, edges, allPceLinks),
723                         getOppPceLink(pathElement - 2, edges, allPceLinks));
724                     double calcOnsrLin = signal.get("calcOnsrLin").doubleValue();
725                     LOG.debug("Loop Path Element = {}, DEGREE, calcOnsrdB= {}",
726                             pathElement, getOsnrDbfromOnsrLin(calcOnsrLin));
727                     if (calcOnsrLin == Double.NEGATIVE_INFINITY || calcOnsrLin == Double.POSITIVE_INFINITY) {
728                         return -1.0;
729                     }
730                     // increment pathElement so that in next step we will not point to Degree2 but
731                     // next node
732                     pathElement--;
733                     LOG.info("Accumulated degradations in the path including ROADM {} + {} are CD: {}; PMD2: "
734                         + "{}; Pdl2 : {}; ONSRdB : {}", currentNode.getNodeId(), nextNode.getNodeId(),
735                         signal.get("calcCd"), signal.get("calcPmd2"), signal.get("calcPdl2"),
736                         getOsnrDbfromOnsrLin(calcOnsrLin));
737                     break;
738                 case XPONDER:
739                     LOG.debug("loop of check OSNR direction AZ: XPDR, Path Element = {}", pathElement);
740                     LOG.error("unsupported back to back transponder configuration");
741                     return -1.0;
742                 default:
743                     LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain");
744             }
745         }
746         PceNode currentNode = allPceNodes.get(new NodeId(vertices.get(0)));
747         LOG.debug("loop of check OSNR direction ZA: Path Element = 0");
748         switch (currentNode.getORNodeType()) {
749             case XPONDER:
750                 LOG.debug("loop of check OSNR direction ZA: XPDR, Path Element = 0");
751                 transponderPresent = true;
752                 // TSP is the last of the path
753                 margin = getLastXpdrMargin(cu, signal, getOppPceLink(0, edges, allPceLinks).getDestTP().getValue(),
754                     serviceType, currentNode, vertices.get(0), 0);
755                 break;
756             case SRG:
757                 LOG.debug("loop of check OSNR direction ZA: SRG, Path Element = 0");
758                 if (getOppPceLink(0, edges, allPceLinks).getlinkType() != OpenroadmLinkType.DROPLINK) {
759                     LOG.error("Error processing Node {} for which input link 0 is not a DROPLINK Type",
760                         currentNode.getNodeId());
761                 }
762                 PceLink pceLink = getOppPceLink(1, edges, allPceLinks);
763                 LOG.info("loop of check OSNR direction ZA: SRG, path Element = 0 CD on preceeding link 1 = {} ps",
764                     pceLink.getcd());
765                 calcDropContrib(cu, signal, currentNode, pceLink);
766                 double calcOnsr = signal.get("calcOnsrLin").doubleValue();
767                 //commented out to avoid spotbug DLS_DEAD_LOCAL_STORE pwrIn = impairments.get("pwrIn");
768                 if (calcOnsr == Double.NEGATIVE_INFINITY || calcOnsr == Double.POSITIVE_INFINITY) {
769                     return -1.0;
770                 }
771                 // If SRG is not the first or the second element of the Path, it is the DROP
772                 // side.
773                 // After accumulated degradations are calculated, we also need to calculate
774                 // resulting OSNR in dB to pass it to the method that verifies end Xponder
775                 // performances are compatible with degradations experienced on the path
776                 try {
777                     calcOnsrdB = getOsnrDbfromOnsrLin(calcOnsr);
778                     LOG.info("checkOSNR loop, last SRG osnr is {} dB", calcOnsrdB);
779                     LOG.info("Loop Path Element = 0, DROP, calcOnsrdB= {}", calcOnsrdB);
780                 } catch (ArithmeticException e) {
781                     LOG.debug("In checkOSNR: OSNR is equal to 0 and the number of links is: {}",
782                         path.getEdgeList().size());
783                     return -1.0;
784                 }
785                 break;
786             case DEGREE:
787             default:
788                 LOG.error("PostAlgoPathValidator.CheckOSNR : unsupported resource type in the path chain last element");
789         }
790         LOG.info("- In checkOSNR: accumulated CD = {} ps, PMD = {} ps, PDL = {} dB, and resulting OSNR calcOnsrdB = {} "
791             + "dB and ONSR dB exterapolated from calcosnrlin = {} including non linear contributions",
792             signal.get("calcCd"), Math.sqrt(signal.get("calcPmd2").doubleValue()),
793             Math.sqrt(signal.get("calcPdl2").doubleValue()), calcOnsrdB,
794             getOsnrDbfromOnsrLin(signal.get("calcOnsrLin").doubleValue()));
795         if (!transponderPresent) {
796             LOG.info("No transponder in the path, User shall check from CD, PMD, and OSNR values provided "
797                 + "that optical tunnel degradations are compatible with external transponder performances");
798             return 0.0;
799         }
800         double delta = margin - SYS_MARGIN;
801         LOG.info("In checkOSNR: Transponder Operational mode results in a residual margin of {} dB, according "
802             + "to CD, PMD and DGD induced penalties and set System Margin of {} dB.",
803             delta, SYS_MARGIN);
804         String validationMessage = delta >= 0 ? "VALIDATED" : "INVALIDATED";
805         LOG.info("- In checkOSNR: Z to A Path from {} to {} {}",
806                 vertices.get(vertices.size() - 1), vertices.get(0), validationMessage);
807         return delta;
808     }
809
810     private String setOpMode(String opMode, String defaultMode) {
811         return
812             opMode == null || opMode.isEmpty() || opMode.contentEquals(StringConstants.UNKNOWN_MODE)
813                 ? defaultMode
814                 : opMode;
815     }
816
817     private PceLink getOppPceLink(Integer pathEltNber, List<PceGraphEdge> edges,
818             Map<LinkId, PceLink> allPceLinks) {
819         return allPceLinks.get(new LinkId(edges.get(pathEltNber).link().getOppositeLink()));
820     }
821
822     private String getXpdrOpMode(String nwTpId, String vertice, int pathElement, PceNode currentNode,
823             String serviceType, CatalogUtils cu) {
824         InstanceIdentifier<TerminationPoint1> nwTpIid =
825             InstanceIdentifiers.createNetworkTerminationPoint1IIDBuilder(vertice, nwTpId);
826         String opMode = cu.getPceOperationalModeFromServiceType(CatalogConstant.CatalogNodeType.TSP, serviceType);
827         try {
828             if (networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, nwTpIid).get().isPresent()) {
829                 // If the operational mode of the Xponder is not consistent nor declared in the topology (Network TP)
830                 opMode = setOpMode(
831                     currentNode.getXponderOperationalMode(
832                         networkTransactionService
833                                 .read(LogicalDatastoreType.CONFIGURATION, nwTpIid)
834                                 .get().orElseThrow().getXpdrNetworkAttributes()),
835                     // Operational mode is found as an attribute of the network TP
836                     opMode);
837                     // Operational mode is retrieved from the service Type assuming it is supported
838                     // by the Xponder
839                 LOG.debug(
840                     "Transponder {} corresponding to path Element {} in the path has {} operational mode",
841                     currentNode.getNodeId().getValue(), pathElement, opMode);
842                 return opMode;
843             }
844         } catch (InterruptedException | ExecutionException e1) {
845             LOG.error("Issue accessing the XponderNetworkAttributes of {} for Transponder {}"
846                 + " corresponding to path Element {} in the path ",
847                 nwTpId, currentNode.getNodeId().getValue(), pathElement);
848         }
849         LOG.info("Did not succeed finding network TP {} in Configuration Datastore. Retrieve"
850             + " default Operational Mode {} from serviceType {}", nwTpId, opMode, serviceType);
851         return opMode;
852     }
853
854     private double getLastXpdrMargin(
855             CatalogUtils cu, Map<String, Double> signal,
856             String nwTpId, String serviceType, PceNode currentNode, String vertice, int pathElement) {
857         LOG.debug("Loop Path Element = {}, Step5.1, XPDR, tries calculating Margin, just before call", pathElement);
858         // Check that accumulated degradations are compatible with TSP performances
859         // According to OpenROADM spec :
860         // margin = cu.getPceRxTspParameters(opMode, calcCd, Math.sqrt(calcPmd2), Math.sqrt(calcPdl2),
861         //              getOsnrDbfromOnsrLin(calcOnsrLin));
862         // Calculation modified for pdl according to calculation in Julia's Tool
863         double calcOnsrdB = getOsnrDbfromOnsrLin(signal.get("calcOnsrLin").doubleValue());
864         LOG.info("Loop Path Element = {}, XPDR, calcosnrdB= {}", pathElement, calcOnsrdB);
865         return cu.getPceRxTspParameters(
866             getXpdrOpMode(nwTpId, vertice, pathElement, currentNode, serviceType, cu),
867             signal.get("calcCd").doubleValue(),
868             Math.sqrt(signal.get("calcPmd2").doubleValue()),
869             Math.sqrt(signal.get("calcPdl2").doubleValue()),
870             calcOnsrdB);
871     }
872
873     private void calcXpdrOSNR(
874             CatalogUtils cu, Map<String, Double> signal, String nwTpId, String serviceType,
875             PceNode currentNode, PceNode nextNode, String vertice, int pathElement) {
876         // If the Xponder operational mode (setOpMode Arg1) is not consistent nor declared in the topology (Network TP)
877         // Operational mode is retrieved from the service Type assuming it is supported by the Xponder (setOpMode Arg2)
878         String opMode = getXpdrOpMode(nwTpId, vertice, pathElement, currentNode, serviceType, cu);
879         // If the operational mode of the ADD/DROP MUX is not consistent nor declared in the topology (Network TP)
880         // Operational mode is set by default to standard opMode for ADD SRGs
881         String adnMode = setOpMode(nextNode.getOperationalMode(), CatalogConstant.MWWRCORE);
882         double calcOnsrLin = cu.getPceTxTspParameters(opMode, adnMode);
883         LOG.debug(
884             "Transponder {} corresponding to path Element {} is connected to SRG which has {} operational mode",
885             currentNode.getNodeId().getValue(), pathElement, adnMode);
886         LOG.info("Transponder {} corresponding to path Element {} in the path has a TX OSNR of {} dB",
887             currentNode.getNodeId().getValue(), pathElement, getOsnrDbfromOnsrLin(calcOnsrLin));
888         // Return the Tx ONSR of the Xponder which results from IB and OOB OSNR contributions
889         // and the spacing associated with Xponder operational mode that is needed to calculate OSNR
890         signal.put("spacing", Double.valueOf(cu.getPceTxTspChannelSpacing(opMode)));
891         signal.put("calcOnsrLin", Double.valueOf(calcOnsrLin));
892     }
893
894     private void calcDropContrib(
895             CatalogUtils cu, Map<String, Double> signal, PceNode currentNode, PceLink pceLink) {
896         //calculation of the SRG contribution for Drop
897         calcLineDegradation(cu, signal, pceLink);
898         Map<String, Double> impairments = cu.getPceRoadmAmpParameters(
899             CatalogConstant.CatalogNodeType.DROP,
900             setOpMode(currentNode.getOperationalMode(), CatalogConstant.MWWRCORE),
901         // If the operational mode of the ADD/DROP MUX is not consistent or not declared in the topology (Network TP)
902         // Operational mode is set by default to standard opMode for ADD/DROP SRGs
903             signal.get("pwrIn").doubleValue(),
904             signal.get("calcCd").doubleValue(),
905             signal.get("calcPmd2").doubleValue(),
906             signal.get("calcPdl2").doubleValue(),
907             signal.get("calcOnsrLin").doubleValue(),
908             signal.get("spacing").doubleValue());
909         signal.putAll(
910             Map.of(
911                 "calcCd", impairments.get("CD"),
912                 "calcPmd2", impairments.get("DGD2"),
913                 "calcPdl2", impairments.get("PDL2"),
914                 "calcOnsrLin", impairments.get("ONSRLIN")));
915     }
916
917     private void calcAddContrib(
918             CatalogUtils cu, Map<String, Double> signal, PceNode currentNode, PceLink pceLink) {
919         //calculation of the SRG contribution for Add
920         String srgMode = setOpMode(currentNode.getOperationalMode(), CatalogConstant.MWWRCORE);
921         // If the operational mode of the ADD/DROP MUX is not consistent or is not declared in the topology (Network TP)
922         // Operational mode is set by default to standard opMode for ADD/DROP SRGs
923         CatalogNodeType cnt = CatalogConstant.CatalogNodeType.ADD;
924         double pwrOut = cu.getPceRoadmAmpOutputPower(
925                 cnt, srgMode, pceLink.getspanLoss(), signal.get("spacing").doubleValue(), pceLink.getpowerCorrection());
926         //calculation of the SRG contribution either for Add and Drop
927         Map<String, Double> impairments = cu.getPceRoadmAmpParameters(cnt, srgMode, 0,
928             signal.get("calcCd").doubleValue(), signal.get("calcPmd2").doubleValue(),
929             signal.get("calcPdl2").doubleValue(),
930             signal.get("calcOnsrLin").doubleValue(), signal.get("spacing").doubleValue());
931         signal.putAll(
932             Map.of(
933                 "calcCd", impairments.get("CD"),
934                 "calcPmd2", impairments.get("DGD2"),
935                 "calcPdl2", impairments.get("PDL2"),
936                 "calcOnsrLin", impairments.get("ONSRLIN"),
937                 "pwrOut", Double.valueOf(pwrOut)));
938     }
939
940     private void calcBypassContrib(CatalogUtils cu, Map<String, Double> signal,
941             PceNode currentNode, PceNode nextNode, PceLink pceLink0, PceLink pceLink1) {
942         // If the operational mode of the Degree is not consistent or declared in the topology
943         // Operational mode is set by default to standard opMode for Degree
944         String degree1Mode = setOpMode(currentNode.getOperationalMode(), CatalogConstant.MWMWCORE);
945         // Same for next node which is the second degree of a ROADM node
946         String degree2Mode = setOpMode(nextNode.getOperationalMode(), CatalogConstant.MWMWCORE);
947         // At that time OpenROADM provides only one spec for the ROADM nodes
948         if (!degree1Mode.equals(degree2Mode)) {
949             LOG.warn("Unsupported Hybrid ROADM configuration with Degree1 {} of {} operational mode and Degree2 "
950                 + "{} of {} operational mode. Will by default use operational mode of Degree2",
951                 currentNode.getNodeId(), degree1Mode, nextNode.getNodeId(), degree2Mode);
952         }
953         calcLineDegradation(cu, signal, pceLink0);
954         CatalogNodeType cnt = CatalogConstant.CatalogNodeType.EXPRESS;
955         double pwrOut = cu.getPceRoadmAmpOutputPower(cnt, degree2Mode, pceLink1.getspanLoss(),
956             signal.get("spacing").doubleValue(), pceLink1.getpowerCorrection());
957         // Adds to accumulated impairments the degradation associated with the Express
958         // path of ROADM : Degree1, express link, Degree2
959         Map<String, Double> impairments = cu.getPceRoadmAmpParameters(cnt, degree2Mode,
960             signal.get("pwrIn").doubleValue(), signal.get("calcCd").doubleValue(),
961             signal.get("calcPmd2").doubleValue(), signal.get("calcPdl2").doubleValue(),
962             signal.get("calcOnsrLin").doubleValue(), signal.get("spacing").doubleValue());
963         signal.putAll(
964             Map.of(
965                 "calcCd", impairments.get("CD"),
966                 "calcPmd2", impairments.get("DGD2"),
967                 "calcPdl2", impairments.get("PDL2"),
968                 "calcOnsrLin", impairments.get("ONSRLIN"),
969                 "pwrOut", Double.valueOf(pwrOut)));
970     }
971     //TODO these methods might be more indicated in a catalog utils refactoring
972
973     private void calcLineDegradation(CatalogUtils cu, Map<String, Double> signal, PceLink pceLink) {
974         // Calculate degradation accumulated across incoming Link and add them to
975         // accumulated impairments
976         // This also includes Non Linear Contribution from the path
977         signal.putAll(Map.of(
978             "pwrIn", Double.valueOf(signal.get("pwrOut").doubleValue() - pceLink.getspanLoss()),
979             "calcCd", Double.valueOf(signal.get("calcCd").doubleValue() + pceLink.getcd()),
980             "calcPmd2", Double.valueOf(signal.get("calcPmd2").doubleValue() + pceLink.getpmd2()),
981             "calcOnsrLin", Double.valueOf(
982                 signal.get("calcOnsrLin").doubleValue()
983                 + cu.calculateNLonsrContribution(
984                     signal.get("pwrOut").doubleValue(), pceLink.getLength(), signal.get("spacing").doubleValue()))));
985     }
986
987     private double getOsnrDbfromOnsrLin(double osnrLu) {
988         return 10 * Math.log10(1 / osnrLu);
989     }
990
991     /**
992      * Get spectrum assignment for path.
993      *
994      * @param path                    the path for which we get spectrum assignment.
995      * @param allPceNodes             all optical nodes.
996      * @param spectralWidthSlotNumber number of slot for spectral width. Depends on
997      *                                service type.
998      * @return a spectrum assignment object which contains begin and end index. If
999      *         no spectrum assignment found, beginIndex = stopIndex = 0
1000      */
1001     private SpectrumAssignment getSpectrumAssignment(GraphPath<String, PceGraphEdge> path,
1002             Map<NodeId, PceNode> allPceNodes, int spectralWidthSlotNumber) {
1003         byte[] freqMap = new byte[GridConstant.NB_OCTECTS];
1004         Arrays.fill(freqMap, (byte) GridConstant.AVAILABLE_SLOT_VALUE);
1005         BitSet result = BitSet.valueOf(freqMap);
1006         boolean isFlexGrid = true;
1007         LOG.debug("Processing path {} with length {}", path, path.getLength());
1008         BitSet pceNodeFreqMap;
1009         Set<PceNode> pceNodes = new LinkedHashSet<>();
1010
1011         for (PceGraphEdge edge : path.getEdgeList()) {
1012             PceLink link = edge.link();
1013             LOG.debug("Processing {} to {}", link.getSourceId().getValue(), link.getDestId().getValue());
1014             if (allPceNodes.containsKey(link.getSourceId())) {
1015                 pceNodes.add(allPceNodes.get(link.getSourceId()));
1016             }
1017             if (allPceNodes.containsKey(link.getDestId())) {
1018                 pceNodes.add(allPceNodes.get(link.getDestId()));
1019             }
1020         }
1021
1022         for (PceNode pceNode : pceNodes) {
1023             LOG.debug("Processing PCE node {}", pceNode);
1024             String pceNodeVersion = pceNode.getVersion();
1025             NodeId pceNodeId = pceNode.getNodeId();
1026             BigDecimal sltWdthGran = pceNode.getSlotWidthGranularity();
1027             BigDecimal ctralFreqGran = pceNode.getCentralFreqGranularity();
1028             if (StringConstants.OPENROADM_DEVICE_VERSION_1_2_1.equals(pceNodeVersion)) {
1029                 LOG.debug("Node {}: version is {} and slot width granularity is {} -> fixed grid mode",
1030                     pceNodeId, pceNodeVersion, sltWdthGran);
1031                 isFlexGrid = false;
1032             }
1033             if (sltWdthGran.setScale(0, RoundingMode.CEILING).equals(GridConstant.SLOT_WIDTH_50)
1034                     && ctralFreqGran.setScale(0, RoundingMode.CEILING).equals(GridConstant.SLOT_WIDTH_50)) {
1035                 LOG.debug("Node {}: version is {} with slot width granularity {} and central "
1036                         + "frequency granularity is {} -> fixed grid mode",
1037                     pceNodeId, pceNodeVersion, sltWdthGran, ctralFreqGran);
1038                 isFlexGrid = false;
1039             }
1040             pceNodeFreqMap = pceNode.getBitSetData();
1041             LOG.debug("Pce node bitset {}", pceNodeFreqMap);
1042             if (pceNodeFreqMap != null) {
1043                 result.and(pceNodeFreqMap);
1044                 LOG.debug("intermediate bitset {}", result);
1045             }
1046
1047         }
1048         LOG.debug("Bitset result {}", result);
1049         return computeBestSpectrumAssignment(result, spectralWidthSlotNumber, isFlexGrid);
1050     }
1051
1052     /**
1053      * Compute spectrum assignment from spectrum occupation for spectral width.
1054      *
1055      * @param spectrumOccupation      the spectrum occupation BitSet.
1056      * @param spectralWidthSlotNumber the nb slots for spectral width.
1057      * @param isFlexGrid              true if flexible grid, false otherwise.
1058      * @return a spectrum assignment object which contains begin and stop index. If
1059      *         no spectrum assignment found, beginIndex = stopIndex = 0
1060      */
1061     private SpectrumAssignment computeBestSpectrumAssignment(
1062             BitSet spectrumOccupation, int spectralWidthSlotNumber, boolean isFlexGrid) {
1063         SpectrumAssignmentBuilder spectrumAssignmentBldr = new SpectrumAssignmentBuilder()
1064             .setBeginIndex(Uint16.valueOf(0))
1065             .setStopIndex(Uint16.valueOf(0))
1066             .setFlexGrid(isFlexGrid);
1067         BitSet referenceBitSet = new BitSet(spectralWidthSlotNumber);
1068         referenceBitSet.set(0, spectralWidthSlotNumber);
1069         //higher is the frequency, smallest is the wavelength number
1070         //in operational, the allocation is done through wavelength starting from the smallest
1071         //so we have to loop from the last element of the spectrum occupation
1072         for (int i = spectrumOccupation.size(); i >= spectralWidthSlotNumber;
1073                 i -= isFlexGrid ? spectralWidthSlotNumber : 1) {
1074             if (spectrumOccupation.get(i - spectralWidthSlotNumber, i).equals(referenceBitSet)) {
1075                 spectrumAssignmentBldr.setBeginIndex(Uint16.valueOf(i - spectralWidthSlotNumber));
1076                 spectrumAssignmentBldr.setStopIndex(Uint16.valueOf(i - 1));
1077                 break;
1078             }
1079         }
1080         return spectrumAssignmentBldr.build();
1081     }
1082
1083     public Double getTpceCalculatedMargin() {
1084         return tpceCalculatedMargin;
1085     }
1086 }