Remove some forced transaction closure
[transportpce.git] / common / src / main / java / org / opendaylight / transportpce / common / catalog / CatalogUtils.java
1 /*
2  * Copyright © 2022 Orange, 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 package org.opendaylight.transportpce.common.catalog;
9
10 import java.util.HashMap;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.Optional;
14 import java.util.concurrent.ExecutionException;
15 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
16 import org.opendaylight.transportpce.common.StringConstants;
17 import org.opendaylight.transportpce.common.catalog.CatalogConstant.CatalogNodeType;
18 import org.opendaylight.transportpce.common.network.NetworkTransactionService;
19 import org.opendaylight.yang.gen.v1.http.org.openroadm.common.link.types.rev191129.RatioDB;
20 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.ImpairmentType;
21 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.amplifier.parameters.Amplifier;
22 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.OpenroadmOperationalModes;
23 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.SpecificOperationalModes;
24 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.Amplifiers;
25 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.Roadms;
26 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.XpondersPluggables;
27 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.xponders.pluggables.XponderPluggableOpenroadmOperationalMode;
28 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.openroadm.operational.modes.xponders.pluggables.XponderPluggableOpenroadmOperationalModeKey;
29 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.specific.operational.modes.SpecificOperationalMode;
30 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.catalog.specific.operational.modes.SpecificOperationalModeKey;
31 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.common.amplifier.drop.parameters.OpenroadmOperationalMode;
32 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.common.amplifier.drop.parameters.OpenroadmOperationalModeKey;
33 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.add.parameters.Add;
34 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.add.parameters.add.AddOpenroadmOperationalMode;
35 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.add.parameters.add.AddOpenroadmOperationalModeKey;
36 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.drop.parameters.Drop;
37 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.roadm.express.parameters.Express;
38 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.transponder.parameters.Penalties;
39 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.transponder.parameters.PenaltiesKey;
40 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.operational.mode.transponder.parameters.TXOOBOsnrKey;
41 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.power.mask.MaskPowerVsPin;
42 import org.opendaylight.yang.gen.v1.http.org.openroadm.operational.mode.catalog.rev211210.power.mask.MaskPowerVsPinKey;
43 import org.opendaylight.yang.gen.v1.http.org.openroadm.service.rev211210.OperationalModeCatalog;
44 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * Utility class for Catalog. Following methods are used to retrieve parameters
50  * from the specification catalog. They point to either openROADM or specific
51  * operational modes. They provide to the PCE, the OLM and the Renderer, the
52  * required parameters to calculate impairments and set output power levels
53  * according to the specifications.
54  *
55  */
56 public class CatalogUtils {
57
58     private static final Logger LOG = LoggerFactory.getLogger(CatalogUtils.class);
59
60     private static final String OPMODE_MISMATCH_MSG =
61         "Operational Mode {} passed to getPceRoadmAmpParameters does not correspond to an OpenROADM mode"
62         + "Parameters for amplifier and/or ROADMs can not be derived from specific-operational-modes.";
63     private static final Map<CatalogConstant.CatalogNodeType, String> CATALOGNODETYPE_OPERATIONMODEID_MAP = Map.of(
64         CatalogConstant.CatalogNodeType.ADD, CatalogConstant.MWWRCORE,
65         CatalogConstant.CatalogNodeType.DROP, CatalogConstant.MWWRCORE,
66         CatalogConstant.CatalogNodeType.EXPRESS, CatalogConstant.MWMWCORE,
67         CatalogConstant.CatalogNodeType.AMP, CatalogConstant.MWISTANDARD);
68     private static final Map<String, String> TSP_DEFAULT_OM_MAP = Map.of(
69         StringConstants.SERVICE_TYPE_100GE_T, CatalogConstant.ORW100GSC,
70         StringConstants.SERVICE_TYPE_OTU4, CatalogConstant.ORW100GSC,
71         StringConstants.SERVICE_TYPE_OTUC2,  CatalogConstant.ORW200GOFEC316GBD,
72         StringConstants.SERVICE_TYPE_OTUC3, CatalogConstant.ORW300GOFEC631GBD,
73         StringConstants.SERVICE_TYPE_OTUC4, CatalogConstant.ORW400GOFEC631GBD,
74         StringConstants.SERVICE_TYPE_400GE, CatalogConstant.ORW400GOFEC631GBD);
75
76     private final PenaltiesComparator penaltiesComparator = new PenaltiesComparator();
77     private NetworkTransactionService networkTransactionService;
78
79     public CatalogUtils(NetworkTransactionService networkTransactionService) {
80         this.networkTransactionService = networkTransactionService;
81     }
82
83     /**
84      * Following method returns default OperationalModeId for devices that do not
85      * expose them.
86      *
87      * @param catalogNodeType
88      *            identifies type of nodes in the catalog
89      * @param serviceType
90      *            allows for Xponder selecting default mode according to the rate
91      *
92      * @return a default operational mode that corresponds to initial specifications
93      *
94      */
95     public String getPceOperationalModeFromServiceType(CatalogConstant.CatalogNodeType catalogNodeType,
96             String serviceType) {
97         if (CATALOGNODETYPE_OPERATIONMODEID_MAP.containsKey(catalogNodeType)) {
98             return CATALOGNODETYPE_OPERATIONMODEID_MAP.get(catalogNodeType);
99         }
100         if (!catalogNodeType.equals(CatalogConstant.CatalogNodeType.TSP)) {
101             LOG.warn("Unsupported catalogNodeType {}", catalogNodeType);
102             return "";
103         }
104         if (!TSP_DEFAULT_OM_MAP.containsKey(serviceType)) {
105             LOG.warn("Unsupported serviceType {} for TSP catalogNodeType", serviceType);
106             return "";
107         }
108         return TSP_DEFAULT_OM_MAP.get(serviceType);
109     }
110
111     /**
112      * This method retrieves channel-spacing associated with a Xponder TX.
113      *
114      * @param operationalModeId
115      *            operational-mode-Id of the Xponder (OR or Specific)
116      *
117      * @return the channel spacing used to correct OSNR contribution values from
118      *         ROADMs and amplifiers
119      * @throws RuntimeException
120      *             if operationalModeId is not described in the catalog
121      */
122
123     public double getPceTxTspChannelSpacing(String operationalModeId) {
124         double baudRate;
125         double maxRollOff;
126         if (operationalModeId.startsWith("OR")) {
127             InstanceIdentifier<XponderPluggableOpenroadmOperationalMode> omCatalogIid = InstanceIdentifier
128                 .builder(OperationalModeCatalog.class)
129                 .child(OpenroadmOperationalModes.class)
130                 .child(XpondersPluggables.class)
131                 .child(XponderPluggableOpenroadmOperationalMode.class,
132                     new XponderPluggableOpenroadmOperationalModeKey(operationalModeId))
133                 .build();
134             try {
135                 Optional<XponderPluggableOpenroadmOperationalMode> omOptional =
136                     networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
137                 if (omOptional.isEmpty()) {
138                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , empty list", omCatalogIid);
139                     return 0.0;
140                 }
141                 XponderPluggableOpenroadmOperationalMode orTspOM = omOptional.get();
142                 maxRollOff = orTspOM.getMaxRollOff() == null ? 0 : orTspOM.getMaxRollOff().doubleValue();
143                 baudRate = orTspOM.getBaudRate().doubleValue();
144             } catch (InterruptedException | ExecutionException e) {
145                 LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
146                 throw new RuntimeException("Operational mode not populated in Catalog : " + omCatalogIid + " :" + e);
147             }
148         } else {
149             // In other cases, means the mode is a non OpenROADM specific Operational Mode
150             InstanceIdentifier<SpecificOperationalMode> omCatalogIid = InstanceIdentifier
151                 .builder(OperationalModeCatalog.class)
152                 .child(SpecificOperationalModes.class)
153                 .child(SpecificOperationalMode.class, new SpecificOperationalModeKey(operationalModeId))
154                 .build();
155             try {
156                 var somOptional =
157                     networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
158                 if (somOptional.isEmpty()) {
159                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , empty list", omCatalogIid);
160                     return 0.0;
161                 }
162                 SpecificOperationalMode speTspOM = somOptional.get();
163                 maxRollOff = speTspOM.getMaxRollOff() == null ? 0 : speTspOM.getMaxRollOff().doubleValue();
164                 baudRate = speTspOM.getBaudRate().doubleValue();
165             } catch (InterruptedException | ExecutionException e) {
166                 LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
167                 throw new RuntimeException("Operational mode not populated in Catalog : " + omCatalogIid + " :" + e);
168             }
169         }
170         if (maxRollOff == 0) {
171             if (CatalogConstant.ORW100GSC.contentEquals(operationalModeId)) {
172             // OR 100G SCFEC is the only case where rolloff factor is not mandatory in the catalog
173                 LOG.info("Operational Mode {} associated channel spacing is 50.0", operationalModeId);
174                 return 50.0;
175             }
176             LOG.warn("Missing rolloff factor (mandatory in Catalog) from Operational Mode {}: use default=0.2",
177                 operationalModeId);
178             maxRollOff = 0.2;
179         }
180         double spacing = 12.5 * Math.ceil(baudRate * (1 + maxRollOff) / 12.5);
181         LOG.info("Operational Mode {} associated channel spacing is {}", operationalModeId, spacing);
182         return spacing;
183     }
184
185     /**
186      * This method retrieves performance parameters associated with a Xponder TX.
187      *
188      * @param operationalModeId
189      *            operational-mode-Id of the Xponder (OR or Specific)
190      * @param addDropMuxOperationalModeId
191      *            operational-mode-Id of the Add-Drop bloc the XponderTX is
192      *            associated to (conditions TX-OOB OSNR value)
193      *
194      * @return the linear Optical Noise to signal Ratio
195      * @throws RuntimeException
196      *             if operationalModeId is not described in the catalog
197      */
198     public double getPceTxTspParameters(String operationalModeId, String addDropMuxOperationalModeId) {
199         double txOnsrLin = 0.0;
200         XponderPluggableOpenroadmOperationalMode orTspOM = null;
201         SpecificOperationalMode speTspOM = null;
202         RatioDB minOOBOsnrSingleChannelValue;
203         RatioDB minOOBOsnrMultiChannelValue;
204         if (operationalModeId.startsWith("OR")) {
205             InstanceIdentifier<XponderPluggableOpenroadmOperationalMode> omCatalogIid = InstanceIdentifier
206                 .builder(OperationalModeCatalog.class)
207                 .child(OpenroadmOperationalModes.class)
208                 .child(XpondersPluggables.class)
209                 .child(XponderPluggableOpenroadmOperationalMode.class,
210                     new XponderPluggableOpenroadmOperationalModeKey(operationalModeId))
211                 .build();
212             try {
213                 Optional<XponderPluggableOpenroadmOperationalMode> omOptional =
214                     networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
215                 if (omOptional.isEmpty()) {
216                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , empty list", omCatalogIid);
217                     return 0.0;
218                 }
219                 orTspOM = omOptional.get();
220                 LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orTspOM);
221                 TXOOBOsnrKey key = new TXOOBOsnrKey(addDropMuxOperationalModeId);
222                 if (orTspOM.getMinTXOsnr() != null) {
223                     txOnsrLin = 1.0 / Math.pow(10.0, orTspOM.getMinTXOsnr().getValue().doubleValue() / 10.0);
224                 }
225                 if (orTspOM.nonnullTXOOBOsnr().get(key) == null) {
226                     return txOnsrLin;
227                 }
228                 minOOBOsnrSingleChannelValue = orTspOM.nonnullTXOOBOsnr().get(key).getMinOOBOsnrSingleChannelValue();
229                 minOOBOsnrMultiChannelValue =  orTspOM.nonnullTXOOBOsnr().get(key).getMinOOBOsnrMultiChannelValue();
230             } catch (InterruptedException | ExecutionException e) {
231                 LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
232                 throw new RuntimeException(
233                     "readMdSal: Error reading from operational store, Operational Mode Catalog : "
234                         + omCatalogIid + " :" + e);
235             }
236         } else {
237             // In other cases, means the mode is a non OpenROADM specific Operational Mode
238             InstanceIdentifier<SpecificOperationalMode> omCatalogIid = InstanceIdentifier
239                 .builder(OperationalModeCatalog.class)
240                 .child(SpecificOperationalModes.class)
241                 .child(SpecificOperationalMode.class, new SpecificOperationalModeKey(operationalModeId))
242                 .build();
243             try {
244                 var somOptional =
245                     networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
246                 if (somOptional.isEmpty()) {
247                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , empty list", omCatalogIid);
248                     return 0.0;
249                 }
250                 speTspOM = somOptional.get();
251                 LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", speTspOM);
252                 TXOOBOsnrKey key = new TXOOBOsnrKey(addDropMuxOperationalModeId);
253                 if (speTspOM.getMinTXOsnr() != null) {
254                     txOnsrLin = 1.0 / Math.pow(10.0, speTspOM.getMinTXOsnr().getValue().doubleValue() / 10.0);
255                 }
256                 if (speTspOM.nonnullTXOOBOsnr().get(key) == null) {
257                     return txOnsrLin;
258                 }
259                 minOOBOsnrSingleChannelValue = speTspOM.nonnullTXOOBOsnr().get(key).getMinOOBOsnrSingleChannelValue();
260                 minOOBOsnrMultiChannelValue = speTspOM.nonnullTXOOBOsnr().get(key).getMinOOBOsnrMultiChannelValue();
261             } catch (InterruptedException | ExecutionException e) {
262                 LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
263                 throw new RuntimeException(
264                     "readMdSal: Error reading from operational store, Operational Mode Catalog : " + omCatalogIid + " :"
265                         + e);
266             }
267         }
268         if (minOOBOsnrSingleChannelValue != null) {
269             txOnsrLin += 1.0 / Math.pow(10.0, minOOBOsnrSingleChannelValue.getValue().doubleValue() / 10.0);
270         }
271         if (minOOBOsnrMultiChannelValue != null) {
272             txOnsrLin += 1.0 / Math.pow(10.0, minOOBOsnrMultiChannelValue.getValue().doubleValue() / 10.0);
273         }
274         return txOnsrLin;
275     }
276
277     /**
278      * This method retrieves performance parameters associated with a Xponder RX.
279      * It calls getRxTspPenalty to evaluate the penalty associated with CD/PMD/PDL
280      * It compares expected OSNR with the OSNR resulting from the line degradation,
281      * and finally calculates and return the resulting margin.
282      *
283      * @param operationalModeId
284      *            operational-mode-Id of the Xponder (OR or Specific)
285      * @param calcCd
286      *            accumulated chromatic dispersion across the line
287      * @param calcPmd
288      *            accumulated Polarization mode dispersion across the line
289      * @param calcPdl
290      *            accumulated Polarization Dependant Loss across the line
291      * @param calcOsnrdB
292      *            Optical Signal to Noise Ratio (dB)resulting from the transmission
293      *            on the line, that shall include the Non Linear contribution
294      *
295      * @return the margin on the service path
296      * @throws RuntimeException
297      *             if operationalModeId is not described in the catalog
298      */
299     public double getPceRxTspParameters(String operationalModeId, double calcCd, double calcPmd,
300             double calcPdl, double calcOsnrdB) {
301         double rxOsnrdB = 0.0;
302         XponderPluggableOpenroadmOperationalMode orTspOM = null;
303         SpecificOperationalMode speTspOM = null;
304         Map<PenaltiesKey, Penalties> penaltiesMap = null;
305         if (operationalModeId.startsWith("OR")) {
306             var omCatalogIid = InstanceIdentifier
307                 .builder(OperationalModeCatalog.class)
308                 .child(OpenroadmOperationalModes.class)
309                 .child(XpondersPluggables.class)
310                 .child(XponderPluggableOpenroadmOperationalMode.class,
311                     new XponderPluggableOpenroadmOperationalModeKey(operationalModeId))
312                 .build();
313             try {
314                 Optional<XponderPluggableOpenroadmOperationalMode> omOptional = networkTransactionService
315                     .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
316                 if (omOptional.isPresent()) {
317                     orTspOM = omOptional.get();
318                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orTspOM);
319                     if (orTspOM.getMinRXOsnrTolerance() != null) {
320                         rxOsnrdB = orTspOM.getMinRXOsnrTolerance().getValue().doubleValue();
321                     }
322                     penaltiesMap = orTspOM.getPenalties();
323                 }
324             } catch (InterruptedException | ExecutionException e) {
325                 LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
326                 throw new RuntimeException(
327                     "readMdSal: Error reading from operational store, Operational Mode Catalog : " + omCatalogIid + " :"
328                         + e);
329             }
330         } else {
331             // In other cases, means the mode is a non OpenROADM specific Operational Mode
332             // InstanceIdentifier<SpecificOperationalMode> omCatalogIid = InstanceIdentifier
333             var omCatalogIid = InstanceIdentifier
334                 .builder(OperationalModeCatalog.class)
335                 .child(SpecificOperationalModes.class)
336                 .child(SpecificOperationalMode.class, new SpecificOperationalModeKey(operationalModeId))
337                 .build();
338             try {
339                 Optional<SpecificOperationalMode> somOptional = networkTransactionService
340                     .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
341                 if (somOptional.isPresent()) {
342                     speTspOM = somOptional.get();
343                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", speTspOM);
344                     if (speTspOM.getMinRXOsnrTolerance() != null) {
345                         rxOsnrdB = speTspOM.getMinRXOsnrTolerance().getValue().doubleValue();
346                     }
347                     penaltiesMap = speTspOM.getPenalties();
348                 }
349             } catch (InterruptedException | ExecutionException e) {
350                 LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist", omCatalogIid);
351                 throw new RuntimeException(
352                     "readMdSal: Error reading from operational store, Operational Mode Catalog : " + omCatalogIid + " :"
353                         + e);
354             }
355         }
356         if (penaltiesMap == null) {
357             LOG.error("Unable to calculate margin as penaltyMap can not be retrieved : Operational mode not populated");
358             return -9999.9;
359         }
360         HashMap<String, Double> impairments = new HashMap<>();
361         double penalty = getRxTspPenalty(calcCd, ImpairmentType.CDPsNm, penaltiesMap);
362         impairments.put("CDpenalty", penalty);
363         double totalPenalty = penalty;
364         penalty = getRxTspPenalty(calcPmd, ImpairmentType.PMDPs, penaltiesMap);
365         impairments.put("PMD Penalty", penalty);
366         totalPenalty += penalty;
367         // Calculation according to OpenROADM specification
368         // penalty = getRxTspPenalty(calcPdl, ImpairmentType.PDLDB, penaltiesMap);
369         // Calculation modified according to Julia's Tool
370         penalty = calcPdl / 2;
371         impairments.put("PDL penalty", penalty);
372         totalPenalty += penalty;
373         // TODO for Future work since at that time we have no way to calculate the following
374         // parameters,even if penalties are defined in the OpenROADM specifications
375         //
376         // impairments.put("Colorless Drop Adjacent Xtalk Penalty", getRxTspPenalty(TBD,
377         // ImpairmentType.ColorlessDropAdjacentChannelCrosstalkGHz, penalitiesMap));
378         // impairments.put("XTalk total Power Penalty", getRxTspPenalty(TBD,
379         // ImpairmentType.CrossTalkTotalPowerDB, penalitiesMap));
380         // impairments.put("Power penalty", getRxTspPenalty(TBD,
381         // ImpairmentType.PowerDBm, penalitiesMap));
382         LOG.info("Penalty resulting from CD, PMD and PDL is {} dB with following contributions {}",
383             totalPenalty, impairments);
384         double margin = calcOsnrdB - totalPenalty - rxOsnrdB;
385         LOG.info("According to RX TSP Specification and calculated impairments Margin is {} dB ", margin);
386         if (margin < 0) {
387             LOG.warn("Negative margin shall result in PCE rejecting the analyzed path");
388         }
389         return margin;
390     }
391
392     /**
393      * This generic method is called from getPceRxTspParameters to provide the
394      * Penalties associated with CD, PMD and DGD for Xponder. It scans a penalty
395      * list that includes penalty values corresponding to an interval between an
396      * upper and a lower boundary for each of the above parameters.
397      *
398      * @param impairmentType
399      *            : the type of impairment (CD/PMD/DGD)
400      * @param calculatedParameter
401      *            calculated accumulated value on the line for the impairment
402      * @param penaltiesMap
403      *            the global map of penalties retrieved by getPceRxTspParameters
404      *            from the Xponder operational mode
405      *
406      * @return the penalty associated with accumulated impairment if it is in the
407      *         range specified in the table, a value that will lead to reject the
408      *         path if this is not the case
409      */
410
411     private double getRxTspPenalty(double calculatedParameter, ImpairmentType impairmentType,
412             Map<PenaltiesKey, Penalties> penalitiesMap) {
413         Penalties penalty = penalitiesMap.values().stream()
414             // We only keep penalties corresponding to the calculated Parameter
415             .filter(val -> val.getParameterAndUnit().getName().equals(impairmentType.getName()))
416             // we sort it according to the comparator (based on up-to-boundary)
417             .sorted(penaltiesComparator)
418             // keep only items for which up to boundary is greater than calculatedParameter
419             .filter(val -> val.getUpToBoundary().doubleValue() >= calculatedParameter)
420             // takes the immediate greater or equal value
421             .findFirst().orElse(null);
422         if (penalty == null) {
423             //means a boundary that is greater than calculatedParameter couldn't be found
424             // Out of specification!
425             return 9999.9;
426         }
427         // In spec, return penalty associated with calculatedParameter
428         LOG.info("Penalty for {} is {} dB", impairmentType, penalty.getPenaltyValue().getValue().doubleValue());
429         return penalty.getPenaltyValue().getValue().doubleValue();
430     }
431
432     /**
433      * This method retrieves performance parameters associated with ROADMs and
434      * Amplifiers. It calculates the contribution of the node to the degradation of
435      * the signal for CD, DGD, PDL, and OSNR which is calculated through a
436      * polynomial fit described in the catalog. It finally corrects the accumulated
437      * values for these parameters and return them.
438      *
439      * @param catalogNodeType
440      *            crossed node path type (ADD/DROP/EXPRESS/AMP)
441      * @param operationalModeId
442      *            operational-mode-Id of the Node (OpenROADM only)
443      * @param cd
444      *            accumulated chromatic dispersion across the line
445      * @param dgd2
446      *            Square of accumulated Group velocity dispersion across the line
447      * @param pdl2
448      *            Square of the accumulated Polarization Dependant Loss across the
449      *            line
450      * @param pwrIn
451      *            Input power required to calculate OSNR contribution of the node =
452      *            f(pwrIn)
453      * @param onsrLin
454      *            Linear Optical Noise to Signal Ratio resulting from the
455      *            transmission on the line, that shall include the Non Linear
456      *            contribution
457      * @param spacing
458      *            Interchannel spacing used for correction to calculate OSNR
459      *            contribution of the node
460      *
461      * @return Impairment, a map that provides corrected values for all calculated
462      *         parameters which includes the contribution of the node
463      *         (CD/DGD2/PDL2/ONSRLin)
464      * @throws RuntimeException
465      *             if operationalModeId is not described in the catalog
466      */
467
468     public Map<String, Double> getPceRoadmAmpParameters(CatalogConstant.CatalogNodeType catalogNodeType,
469             String operationalModeId, double pwrIn, double cd, double dgd2, double pdl2,
470             double onsrLin, double spacing) {
471         double maxIntroducedCd;
472         double maxIntroducedPdl;
473         double maxIntroducedDgd;
474         List<Double> osnrPolynomialFits;
475         switch (catalogNodeType) {
476             case ADD:
477                 var omCatalogIid = InstanceIdentifier
478                     .builder(OperationalModeCatalog.class)
479                     .child(OpenroadmOperationalModes.class)
480                     .child(Roadms.class)
481                     .child(Add.class)
482                     .child(AddOpenroadmOperationalMode.class, new AddOpenroadmOperationalModeKey(operationalModeId))
483                     .build();
484                 try {
485                     var omOptional =
486                         networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
487                     if (omOptional.isEmpty()) {
488                         LOG.error(OPMODE_MISMATCH_MSG, operationalModeId);
489                         return new HashMap<>();
490                     }
491                     var orAddOM = omOptional.get();
492                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orAddOM);
493                     maxIntroducedCd = orAddOM.getMaxIntroducedCd().doubleValue();
494                     // As per current OpenROADM Spec
495                     //maxIntroducedPdl = orAddOM.getMaxIntroducedPdl().getValue().doubleValue();
496                     // Applying calculation as provided in Julia's tool
497                     maxIntroducedPdl = Math.sqrt(0.2 * 0.2 + 0.4 * 0.4);
498                     maxIntroducedDgd = orAddOM.getMaxIntroducedDgd().doubleValue();
499                     osnrPolynomialFits = List.of(orAddOM.getIncrementalOsnr().getValue().doubleValue());
500                 } catch (InterruptedException | ExecutionException e) {
501                     onsrLin = 1;
502                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
503                         omCatalogIid);
504                     throw new RuntimeException(
505                         "readMdSal: Error reading from operational store, Operational Mode Catalog : "
506                             + omCatalogIid + " :" + e);
507                 }
508                 break;
509
510             case DROP:
511                 var omCatalogIid1 = InstanceIdentifier
512                     .builder(OperationalModeCatalog.class)
513                     .child(OpenroadmOperationalModes.class)
514                     .child(Roadms.class)
515                     .child(Drop.class)
516                     .child(OpenroadmOperationalMode.class, new OpenroadmOperationalModeKey(operationalModeId))
517                     .build();
518                 try {
519                     var omOptional =
520                         networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid1).get();
521                     if (omOptional.isEmpty()) {
522                         LOG.error(OPMODE_MISMATCH_MSG, operationalModeId);
523                         return new HashMap<>();
524                     }
525                     var orDropOM = omOptional.get();
526                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orDropOM);
527                     maxIntroducedCd = orDropOM.getMaxIntroducedCd().doubleValue();
528                     // As per current OpenROADM Spec
529                     // maxIntroducedPdl = orDropOM.getMaxIntroducedPdl().getValue().doubleValue();
530                     // Applying calculation as provided in Julia's tool
531                     maxIntroducedPdl = Math.sqrt(0.2 * 0.2 + 0.4 * 0.4);
532                     maxIntroducedDgd = orDropOM.getMaxIntroducedDgd().doubleValue();
533                     osnrPolynomialFits = List.of(
534                         orDropOM.getOsnrPolynomialFit().getD().doubleValue(),
535                         orDropOM.getOsnrPolynomialFit().getC().doubleValue(),
536                         orDropOM.getOsnrPolynomialFit().getB().doubleValue(),
537                         orDropOM.getOsnrPolynomialFit().getA().doubleValue());
538                 } catch (InterruptedException | ExecutionException e) {
539                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
540                         omCatalogIid1);
541                     throw new RuntimeException(
542                         "readMdSal: Error reading from operational store, Operational Mode Catalog : "
543                             + omCatalogIid1 + " :" + e);
544                 }
545                 break;
546
547             case EXPRESS:
548                 var omCatalogIid2 = InstanceIdentifier
549                     .builder(OperationalModeCatalog.class)
550                     .child(OpenroadmOperationalModes.class)
551                     .child(Roadms.class)
552                     .child(Express.class)
553                     .child(
554                         org.opendaylight.yang.gen.v1.http
555                             .org.openroadm.operational.mode.catalog.rev211210
556                             .operational.mode.roadm.express.parameters.express.OpenroadmOperationalMode.class,
557                         new org.opendaylight.yang.gen.v1.http
558                             .org.openroadm.operational.mode.catalog.rev211210
559                             .operational.mode.roadm.express.parameters.express.OpenroadmOperationalModeKey(
560                                 operationalModeId))
561                     .build();
562                 try {
563                     var omOptional = networkTransactionService
564                         .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid2)
565                         .get();
566                     if (omOptional.isEmpty()) {
567                         LOG.error(OPMODE_MISMATCH_MSG, operationalModeId);
568                         return new HashMap<>();
569                     }
570                     var orExpressOM = omOptional.get();
571                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orExpressOM);
572                     maxIntroducedCd = orExpressOM.getMaxIntroducedCd().doubleValue();
573                     // As per current OpenROADM Spec
574                     // maxIntroducedPdl = orExpressOM.getMaxIntroducedPdl().getValue().doubleValue();
575                     // Applying calculation as provided in Julia's tool
576                     maxIntroducedPdl = Math.sqrt(2 * 0.2 * 0.2 + 2 * 0.4 * 0.4);
577                     maxIntroducedDgd = orExpressOM.getMaxIntroducedDgd().doubleValue();
578                     osnrPolynomialFits = List.of(
579                         orExpressOM.getOsnrPolynomialFit().getD().doubleValue(),
580                         orExpressOM.getOsnrPolynomialFit().getC().doubleValue(),
581                         orExpressOM.getOsnrPolynomialFit().getB().doubleValue(),
582                         orExpressOM.getOsnrPolynomialFit().getA().doubleValue());
583                 } catch (InterruptedException | ExecutionException e) {
584                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
585                         omCatalogIid2);
586                     throw new RuntimeException(
587                         "readMdSal: Error reading from operational store, Operational Mode Catalog : "
588                             + omCatalogIid2 + " :" + e);
589                 }
590                 break;
591
592             case AMP:
593                 var omCatalogIid3 = InstanceIdentifier
594                     .builder(OperationalModeCatalog.class)
595                     .child(OpenroadmOperationalModes.class)
596                     .child(Amplifiers.class)
597                     .child(Amplifier.class)
598                     .child(OpenroadmOperationalMode.class, new OpenroadmOperationalModeKey(operationalModeId))
599                     .build();
600                 try {
601                     var omOptional = networkTransactionService
602                         .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid3)
603                         .get();
604                     if (omOptional.isEmpty()) {
605                         LOG.error(OPMODE_MISMATCH_MSG, operationalModeId);
606                         return new HashMap<>();
607                     }
608                     var orAmpOM = omOptional.get();
609                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orAmpOM);
610                     maxIntroducedCd = orAmpOM.getMaxIntroducedCd().doubleValue();
611                     // As per current OpenROADM Spec
612                     // maxIntroducedPdl = orAmpOM.getMaxIntroducedPdl().getValue().doubleValue();
613                     // Applying calculation as provided in Julia's tool
614                     maxIntroducedPdl = 0.2;
615                     maxIntroducedDgd = orAmpOM.getMaxIntroducedDgd().doubleValue();
616                     osnrPolynomialFits = List.of(
617                         orAmpOM.getOsnrPolynomialFit().getD().doubleValue(),
618                         orAmpOM.getOsnrPolynomialFit().getC().doubleValue(),
619                         orAmpOM.getOsnrPolynomialFit().getB().doubleValue(),
620                         orAmpOM.getOsnrPolynomialFit().getA().doubleValue());
621                 } catch (InterruptedException | ExecutionException e) {
622                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
623                         omCatalogIid3);
624                     throw new RuntimeException(
625                         "readMdSal: Error reading from operational store, Operational Mode Catalog : "
626                             + omCatalogIid3 + " :" + e);
627                 }
628                 break;
629             default:
630                 LOG.error("Unsupported catalogNodeType {}", catalogNodeType);
631                 return new HashMap<>();
632         }
633         cd += maxIntroducedCd;
634         pdl2 += Math.pow(maxIntroducedPdl, 2.0);
635         dgd2 += Math.pow(maxIntroducedDgd, 2.0);
636         double pwrFact = 1;
637         double contrib = 0;
638         // We correct PwrIn to the value corresponding to a 50 GHz Bandwidth, because OpenROADM spec (polynomial fit)
639         // is based on power in 50GHz Bandwidth
640         pwrIn -= 10 * Math.log10(spacing / 50.0);
641         if (catalogNodeType != CatalogNodeType.ADD) {
642             // For add, incremental OSNR is defined for Noiseless input, BW Correction (contrib) does not apply
643             contrib = 10 * Math.log10(spacing / 50.0);
644         }
645         for (double fit : osnrPolynomialFits) {
646             contrib += pwrFact * fit;
647             pwrFact *= pwrIn;
648             // Using a for loop with multiplication instead of Math.pow optimizes the computation.
649         }
650         // Double is not strictly spoken a Mathematics commutative group because
651         // computers design limits their bits representation size.
652         // As a result, the order of arithmetic operation matters.
653         // In a sum, smallest numbers should be introduced first for a maximum of
654         // precision. In other words, the sum
655         //    10 * Math.log10(spacing / 50.0)
656         //    + osnrPolynomialFits.get(0)
657         //    + osnrPolynomialFits.get(1) * pwrIn
658         //    + osnrPolynomialFits.get(2) * Math.pow(pwrIn, 2)
659         //    + osnrPolynomialFits.get(3) * Math.pow(pwrIn, 3)
660         // is not equal to its reverse form
661         //    osnrPolynomialFits.get(3) * Math.pow(pwrIn, 3)
662         //    + osnrPolynomialFits.get(2) * Math.pow(pwrIn, 2)
663         //    + osnrPolynomialFits.get(1) * pwrIn
664         //    + osnrPolynomialFits.get(0)
665         //    + 10 * Math.log10(spacing / 50.0)
666         // and the more precise first form should be preferred here.
667         onsrLin += Math.pow(10, -contrib / 10);
668         Map<String, Double> impairments = new HashMap<>();
669         impairments.put("CD", cd);
670         impairments.put("DGD2", dgd2);
671         impairments.put("PDL2", pdl2);
672         impairments.put("ONSRLIN", onsrLin);
673         LOG.info("Accumulated CD is {} ps, DGD is {} ps and PDL is {} dB", cd, Math.sqrt(dgd2), Math.sqrt(pdl2));
674         LOG.info("Resulting OSNR is {} dB", 10 * Math.log10(1 / onsrLin));
675         return impairments;
676     }
677
678     /**
679      * This method calculates power that shall be applied at the output of ROADMs and
680      * Amplifiers. It retrieves the mask-power-vs-Pin and calculates target output
681      * power from the span loss
682      *
683      * @param catalogNodeType
684      *            crossed node path type (ADD/EXPRESS/AMP)
685      * @param operationalModeId
686      *            operational-mode-Id of the Node (OpenROADM only)
687      * @param spanLoss
688      *            spanLoss at the output of the ROADM
689      * @param powerCorrection
690      *            correction to be applied to the calculated power according to fiber type
691      * @param spacing
692      *            Interchannel spacing used for correction to calculate output power
693      * @return outputPower
694      *         Corrected output power calculated according to channel spacing
695      * @throws RuntimeException
696      *             if operationalModeId is not described in the catalog
697      */
698     public double getPceRoadmAmpOutputPower(CatalogConstant.CatalogNodeType catalogNodeType,
699             String operationalModeId, double spanLoss, double spacing, double powerCorrection) {
700         double pout = 99999.0;
701         switch (catalogNodeType) {
702             case ADD:
703                 var omCatalogIid = InstanceIdentifier
704                     .builder(OperationalModeCatalog.class)
705                     .child(OpenroadmOperationalModes.class)
706                     .child(Roadms.class)
707                     .child(Add.class)
708                     .child(AddOpenroadmOperationalMode.class, new AddOpenroadmOperationalModeKey(operationalModeId))
709                     .build();
710                 try {
711                     var omOptional =
712                         networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get();
713                     if (omOptional.isEmpty()) {
714                         LOG.error(OPMODE_MISMATCH_MSG, operationalModeId);
715                         return pout;
716                     }
717                     var orAddOM = omOptional.get();
718                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orAddOM);
719                     var mask = orAddOM.getMaskPowerVsPin();
720                     for (Map.Entry<MaskPowerVsPinKey, MaskPowerVsPin> pw : mask.entrySet()) {
721                         if (spanLoss >= pw.getKey().getLowerBoundary().doubleValue()
722                             && spanLoss <= pw.getKey().getUpperBoundary().doubleValue()) {
723                             pout = pw.getValue().getC().doubleValue() * spanLoss + pw.getValue().getD().doubleValue()
724                                 + powerCorrection + 10 * Math.log10(spacing / 50.0);
725                             LOG.info("Calculated target Output power is {} dB in {} Bandwidth", pout, spacing);
726                             return pout;
727                         }
728                     }
729                     LOG.info("Did not succeed in calculating target Output power, SpanLoss {} dB is out of range",
730                         spanLoss);
731                 } catch (InterruptedException | ExecutionException e) {
732                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
733                         omCatalogIid);
734                     throw new RuntimeException(
735                         "readMdSal: Error reading from operational store, Operational Mode Catalog : "
736                             + omCatalogIid + " :" + e);
737                 }
738                 break;
739
740             case EXPRESS:
741                 var omCatalogIid2 = InstanceIdentifier
742                     .builder(OperationalModeCatalog.class)
743                     .child(OpenroadmOperationalModes.class)
744                     .child(Roadms.class)
745                     .child(Express.class)
746                     .child(
747                         org.opendaylight.yang.gen.v1.http
748                             .org.openroadm.operational.mode.catalog.rev211210
749                             .operational.mode.roadm.express.parameters.express.OpenroadmOperationalMode.class,
750                         new org.opendaylight.yang.gen.v1.http
751                             .org.openroadm.operational.mode.catalog.rev211210
752                             .operational.mode.roadm.express.parameters.express.OpenroadmOperationalModeKey(
753                                 operationalModeId))
754                     .build();
755                 try {
756                     var omOptional = networkTransactionService
757                         .read(LogicalDatastoreType.CONFIGURATION, omCatalogIid2)
758                         .get();
759                     if (omOptional.isEmpty()) {
760                         LOG.error(OPMODE_MISMATCH_MSG, operationalModeId);
761                         return pout;
762                     }
763                     var orExpressOM = omOptional.get();
764                     LOG.debug("readMdSal: Operational Mode Catalog: omOptional.isPresent = true {}", orExpressOM);
765                     var mask = orExpressOM.getMaskPowerVsPin();
766                     for (Map.Entry<MaskPowerVsPinKey, MaskPowerVsPin> pw : mask.entrySet()) {
767                         if (spanLoss >= pw.getKey().getLowerBoundary().doubleValue()
768                                 && spanLoss <= pw.getKey().getUpperBoundary().doubleValue()) {
769                             pout = pw.getValue().getC().doubleValue() * spanLoss + pw.getValue().getD().doubleValue()
770                                 + powerCorrection + 10 * Math.log10(spacing / 50.0);
771                             LOG.info("Calculated target Output power is {} dB in {} Bandwidth", pout, spacing);
772                             return pout;
773                         }
774                     }
775                     LOG.info("Did not succeed in calculating target Output power, SpanLoss {} dB is out of range",
776                         spanLoss);
777                 } catch (InterruptedException | ExecutionException e) {
778                     LOG.error("readMdSal: Error reading Operational Mode Catalog {} , Mode does not exist",
779                         omCatalogIid2);
780                     throw new RuntimeException(
781                         "readMdSal: Error reading from operational store, Operational Mode Catalog : "
782                             + omCatalogIid2 + " :" + e);
783                 }
784                 break;
785
786             default:
787                 LOG.error("Unsupported catalogNodeType {}", catalogNodeType);
788         }
789         return pout;
790     }
791
792     /**
793      * Non linear contribution computation.
794      * Public method calculating non linear contribution among the path from
795      * launched power and span length Formula comes from
796      * OpenROADM_OSNR_Calculation_20220610 Tool The resulting contribution shall be
797      * calculated for each fiber span and summed up
798      * @param launchedPowerdB
799      *            The power launched in the span (shall account for Optical Distribution
800      *            Frame loss)
801      * @param spanLength
802      *            Length of the span in km
803      * @param spacing
804      *            OpenROADM power and osnr contribution calculations are based on
805      *            spacing between channels : the Media Channel (MC) width
806      *
807      * @return nonLinearOnsrContributionLin
808      *         The inverse of the NL OSNR contribution converted from dB to linear value
809      */
810     public double calculateNLonsrContribution(double launchedPowerdB, double spanLength, double spacing) {
811         double constanteC0 = 0 ;
812         if (spacing > 162.5) {
813             constanteC0 = CatalogConstant.NLCONSTANTC0GT1625;
814         } else if (spacing > 112.5) {
815             constanteC0 = CatalogConstant.NLCONSTANTC0UPTO1625;
816         } else if (spacing > 100.0) {
817             constanteC0 = CatalogConstant.NLCONSTANTC0UPTO1125;
818         } else if (spacing > 87.5) {
819             constanteC0 = CatalogConstant.NLCONSTANTC0UPTO1000;
820         } else {
821             constanteC0 = CatalogConstant.NLCONSTANTC0UPTO875;
822         }
823         double nonLinearOnsrContributionLinDb = launchedPowerdB * CatalogConstant.NLCONSTANTC1
824             + constanteC0 + CatalogConstant.NLCONSTANTCE * Math.exp(CatalogConstant.NLCONSTANTEX * spanLength);
825         LOG.info(" OSNR Non Linear contribution is {} dB", nonLinearOnsrContributionLinDb);
826         return Math.pow(10.0, -nonLinearOnsrContributionLinDb / 10);
827     }
828
829     public boolean isCatalogFilled() {
830         var omCatalogIid = InstanceIdentifier
831             .builder(OperationalModeCatalog.class)
832             .child(OpenroadmOperationalModes.class)
833             .child(Roadms.class)
834             .child(Add.class)
835             .child(AddOpenroadmOperationalMode.class, new AddOpenroadmOperationalModeKey(CatalogConstant.MWWRCORE))
836             .build();
837         try {
838             if (networkTransactionService.read(LogicalDatastoreType.CONFIGURATION, omCatalogIid).get().isEmpty()) {
839                 LOG.error("Operational Mode catalog is not filled");
840                 return false;
841             }
842             return true;
843         } catch (InterruptedException | ExecutionException e) {
844             LOG.error("readMdSal: Error reading Operational Mode Catalog, catalog not filled");
845             throw new RuntimeException(
846                 "readMdSal: Error reading from operational store, Operational Mode Catalog not filled" + e);
847         }
848     }
849
850 }