001/******************************************************************************* 002 * Copyright (c) 2017 Pablo Pavon Marino and others. 003 * All rights reserved. This program and the accompanying materials 004 * are made available under the terms of the 2-clause BSD License 005 * which accompanies this distribution, and is available at 006 * https://opensource.org/licenses/BSD-2-Clause 007 * 008 * Contributors: 009 * Pablo Pavon Marino and others - initial API and implementation 010 *******************************************************************************/ 011 012 013 014 015 016 017 018 019 020 021package com.net2plan.examples.ocnbook.onlineSim; 022 023import java.util.ArrayList; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Map; 029import java.util.Random; 030import java.util.Set; 031 032import com.net2plan.interfaces.networkDesign.Demand; 033import com.net2plan.interfaces.networkDesign.Link; 034import com.net2plan.interfaces.networkDesign.Net2PlanException; 035import com.net2plan.interfaces.networkDesign.NetPlan; 036import com.net2plan.interfaces.networkDesign.NetworkLayer; 037import com.net2plan.interfaces.networkDesign.Node; 038import com.net2plan.interfaces.networkDesign.Route; 039import com.net2plan.interfaces.simulation.IEventProcessor; 040import com.net2plan.interfaces.simulation.SimEvent; 041import com.net2plan.utils.Constants.RoutingType; 042import com.net2plan.utils.InputParameter; 043import com.net2plan.utils.Pair; 044import com.net2plan.utils.RandomUtils; 045import com.net2plan.utils.Triple; 046 047import cern.colt.matrix.tdouble.DoubleFactory1D; 048import cern.colt.matrix.tdouble.DoubleMatrix1D; 049 050/** 051 * Implements the reactions of a technology-agnostic network to connection requests under various CAC options, and reactions to failures and repairs under different recovery schemes. 052 * 053 * The algorithm reacts to the following events: 054 * <ul> 055 * <li>SimEvent.RouteAdd: Adds a route associated to the given demand. This can mean creating also a backup route if the 1+1 protection options are active. If there is not enough resources for the route, it is not created.</li> 056 * <li>SimEvent.RouteRemove: Removes the corresponding Route object, and any associated backup route.</li> 057 * <li>SimEvent.DemandModify: Modifies the offered traffic of a demand, caused by a traffic fluctuation.</li> 058 * <li>SimEvent.NodesAndLinksChangeFailureState: Fails/repairs the indicated nodes and/or links, and reacts to such failures (the particular form depends on the network recovery options selected).</li> 059 * </ul> 060 * 061 * This module can be used to simulate a number of Connection-Admission-Control strategies, that decide on the route if the incoming connection requests. 062 * This can be done separately or in conjuntion with different network recovery schemes that can be chosen, that define how the network reacts to node/link failures and repairs. 063 * 064 * This module can be used in conjunction with the {@code Online_evGen_generalGenerator} generator for simulating networks. 065 * 066 * @net2plan.keywords CAC (Connection-Admission-Control), Network recovery: protection, Network recovery: restoration 067 * @net2plan.ocnbooksections Section 3.3.3, Exercise 3.7, Exercise 3.8 068 * @net2plan.inputParameters 069 * @author Pablo Pavon-Marino 070 */ 071public class Online_evProc_generalProcessor extends IEventProcessor 072{ 073 private InputParameter cacType = new InputParameter ("cacType", "#select# alternate-routing least-congested-routing load-sharing" , "Criteria to decide the route of a connection among the available paths"); 074 private InputParameter shortestPathType = new InputParameter ("shortestPathType", "#select# hops km" , "Criteria to compute the shortest path. Valid values: 'hops' or 'km'"); 075 private InputParameter randomSeed = new InputParameter ("randomSeed", (long) 1 , "Seed for the random generator (-1 means random)"); 076 private InputParameter k = new InputParameter ("k", (int) 2 , "Maximum number of admissible paths per demand, from where to choose their route" , 1 , Integer.MAX_VALUE); 077 private InputParameter maxLengthInKm = new InputParameter ("maxLengthInKm", (double) -1 , "Paths longer than this are considered not admissible. A non-positive number means this limit does not exist"); 078 private InputParameter maxNumHops = new InputParameter ("maxNumHops", (int) -1 , "The path from an origin to any destination in cannot have more than this number of hops. A non-positive number means this limit does not exist"); 079 private InputParameter layerId = new InputParameter ("layerId", (long) -1 , "Layer containing traffic demands (-1 means default layer)"); 080 private InputParameter recoveryType = new InputParameter ("recoveryType", "#select# protection restoration none" , "None, nothing is done, so affected routes fail. Restoration, affected routes are visited sequentially, and we try to reroute them in the available capacity; in protection, affected routes are rerouted using the backup routes."); 081 private InputParameter removePreviousRoutes = new InputParameter ("removePreviousRoutes", false , "If true, previous routes are removed from the system."); 082 private InputParameter protectionTypeToNewRoutes = new InputParameter ("protectionTypeToNewRoutes", "#select# 1+1-link-disjoint 1+1-node-disjoint srg-disjoint none" , "The new routes to add, may be associated a 1+1 protection (link, node or SRG disjoint), or no protection is added (none)"); 083 084 private List<Node> nodes; 085 private NetworkLayer layer; 086 private Map<Route,List<Link>> routeOriginalLinks; 087 private Map<Pair<Node,Node>,List<List<Link>>> cpl; 088 private Map<Pair<Node,Node>,List<Pair<List<Link>,List<Link>>>> cpl11; 089 090 private boolean newRoutesHave11Protection; 091 private boolean isRestorationRecovery , isProtectionRecovery , isShortestPathNumHops; 092 private boolean isAlternateRouting , isLeastCongestedRouting , isLoadSharing; 093 private Random rng; 094 095 private double stat_trafficOfferedConnections , stat_trafficCarriedConnections; 096 private double stat_trafficAttemptedToRecoverConnections , stat_trafficSuccessfullyRecoveredConnections; 097 private long stat_numOfferedConnections , stat_numCarriedConnections; 098 private long stat_numAttemptedToRecoverConnections , stat_numSuccessfullyRecoveredConnections; 099 private double stat_transitoryInitTime; 100 101 102 @Override 103 public String getDescription() 104 { 105 return "Implements the reactions of a technology-agnostic network to connection requests under various CAC options, and reactions to failures and repairs under different recovery schemes."; 106 } 107 108 109 110 @Override 111 public List<Triple<String, String, String>> getParameters() 112 { 113 /* Returns the parameter information for all the InputParameter objects defined in this object (uses Java reflection) */ 114 return InputParameter.getInformationAllInputParameterFieldsOfObject(this); 115 } 116 117 @Override 118 public void initialize(NetPlan initialNetPlan, Map<String, String> algorithmParameters, Map<String, String> simulationParameters, Map<String, String> net2planParameters) 119 { 120 /* Initialize all InputParameter objects defined in this object (this uses Java reflection) */ 121 InputParameter.initializeAllInputParameterFieldsOfObject(this, algorithmParameters); 122 123 this.layer = layerId.getLong () == -1? initialNetPlan.getNetworkLayerDefault() : initialNetPlan.getNetworkLayerFromId(layerId.getLong ()); 124 this.nodes = initialNetPlan.getNodes (); 125 this.isShortestPathNumHops = shortestPathType.getString ().equalsIgnoreCase("hops"); 126 this.routeOriginalLinks = new HashMap<Route,List<Link>> (); 127 this.isRestorationRecovery = recoveryType.getString ().equalsIgnoreCase("restoration"); 128 this.isProtectionRecovery = recoveryType.getString ().equalsIgnoreCase("protection"); 129 this.isAlternateRouting = cacType.getString().equalsIgnoreCase("alternate-routing"); 130 this.isLeastCongestedRouting = cacType.getString().equalsIgnoreCase("least-congested-routing"); 131 this.isLoadSharing = cacType.getString().equalsIgnoreCase("load-sharing"); 132 this.newRoutesHave11Protection = !protectionTypeToNewRoutes.getString ().equalsIgnoreCase("none"); 133 this.rng = new Random(randomSeed.getLong () == -1? (long) RandomUtils.random(0, Long.MAX_VALUE - 1) : randomSeed.getLong ()); 134 if (!isProtectionRecovery && newRoutesHave11Protection) throw new Net2PlanException ("In the input parameter you ask to assign protection paths to new connections, while the recovery type chosen does not use them"); 135 136 /* Compute the candidate path list */ 137 final int E = initialNetPlan.getNumberOfLinks(layer); 138 final DoubleMatrix1D linkCostVector = isShortestPathNumHops? DoubleFactory1D.dense.make (E , 1.0) : initialNetPlan.getVectorLinkLengthInKm(); 139 this.cpl = initialNetPlan.computeUnicastCandidatePathList(linkCostVector , k.getInt(), maxLengthInKm.getDouble(), maxNumHops.getInt(), -1, -1, -1, -1 , null); 140 final int protectionTypeCode = protectionTypeToNewRoutes.equals("srg-disjoint") ? 0 : protectionTypeToNewRoutes.equals("1+1-node-disjoint")? 1 : 2; 141 this.cpl11 = !newRoutesHave11Protection? null : NetPlan.computeUnicastCandidate11PathList(cpl, protectionTypeCode); 142 143 144 145 if (removePreviousRoutes.getBoolean()) 146 { 147 initialNetPlan.removeAllUnicastRoutingInformation(layer); 148 initialNetPlan.removeAllMulticastTrees(layer); 149 } 150 151 initialNetPlan.setRoutingTypeAllDemands(RoutingType.SOURCE_ROUTING); 152 for (Route r : initialNetPlan.getRoutesAreNotBackup(layer)) routeOriginalLinks.put (r , r.getSeqLinks()); 153 this.finishTransitory(0); 154 } 155 156 @Override 157 public void processEvent(NetPlan currentNetPlan, SimEvent event) 158 { 159 if (event.getEventObject () instanceof SimEvent.RouteAdd) 160 { 161 SimEvent.RouteAdd addRouteEvent = (SimEvent.RouteAdd) event.getEventObject (); 162 if (addRouteEvent.demand.getLayer() != this.layer) throw new Net2PlanException ("Routes cannot be added at layers different to layer " + layerId.getLong ()); 163 164 /* update the offered traffic of the demand */ 165 this.stat_numOfferedConnections ++; 166 this.stat_trafficOfferedConnections += addRouteEvent.carriedTraffic; 167 168 /* Computes one or two paths over the links (the second path would be a backup route). */ 169 if (newRoutesHave11Protection) 170 { 171 Pair<List<Link>,List<Link>> spLinks = computeValid11PathPairNewRoute(addRouteEvent.demand , addRouteEvent.occupiedLinkCapacity); 172 if (spLinks != null) 173 { 174 final Route addedRoute = currentNetPlan.addRoute(addRouteEvent.demand , addRouteEvent.carriedTraffic , addRouteEvent.occupiedLinkCapacity, spLinks.getFirst() , null); 175 final Route addedBackupRoute = currentNetPlan.addRoute(addRouteEvent.demand , 0 , addRouteEvent.occupiedLinkCapacity , spLinks.getSecond() , null); 176 addedRoute.addBackupRoute(addedBackupRoute); 177 addRouteEvent.routeAddedToFillByProcessor = addedRoute; 178 this.routeOriginalLinks.put (addedRoute , spLinks.getFirst()); 179 this.stat_numCarriedConnections ++; 180 this.stat_trafficCarriedConnections += addRouteEvent.carriedTraffic; 181 } 182 } 183 else 184 { 185 List<Link> spLinks = computeValidPathNewRoute(addRouteEvent.demand , addRouteEvent.occupiedLinkCapacity); 186 if (!spLinks.isEmpty()) 187 { 188 final Route addedRoute = currentNetPlan.addRoute(addRouteEvent.demand , addRouteEvent.carriedTraffic , addRouteEvent.occupiedLinkCapacity, spLinks , null); 189 this.routeOriginalLinks.put (addedRoute , spLinks); 190 this.stat_numCarriedConnections ++; 191 this.stat_trafficCarriedConnections += addRouteEvent.carriedTraffic; 192 addRouteEvent.routeAddedToFillByProcessor = addedRoute; 193 } 194 } 195 196 } 197 else if (event.getEventObject () instanceof SimEvent.RouteRemove) 198 { 199 SimEvent.RouteRemove routeEvent = (SimEvent.RouteRemove) event.getEventObject (); 200 Route routeToRemove = routeEvent.route; 201 if (routeToRemove == null) throw new RuntimeException ("Bad"); 202 for (Route backup : new ArrayList<> (routeToRemove.getBackupRoutes())) backup.remove (); 203 routeToRemove.remove(); 204 this.routeOriginalLinks.remove(routeToRemove); 205 } else if (event.getEventObject () instanceof SimEvent.DemandModify) 206 { 207 SimEvent.DemandModify ev = (SimEvent.DemandModify) event.getEventObject (); 208 Demand d = ev.demand; 209 if (ev.modificationIsRelativeToCurrentOfferedTraffic) 210 d.setOfferedTraffic(d.getOfferedTraffic() + ev.offeredTraffic); 211 else 212 d.setOfferedTraffic(ev.offeredTraffic); 213 } else if (event.getEventObject () instanceof SimEvent.NodesAndLinksChangeFailureState) 214 { 215 SimEvent.NodesAndLinksChangeFailureState ev = (SimEvent.NodesAndLinksChangeFailureState) event.getEventObject (); 216 217 /* This automatically sets as up the routes affected by a repair in its current path, and sets as down the affected by a failure in its current path */ 218 Set<Route> routesFromDownToUp = new HashSet<> (currentNetPlan.getRoutesDown()); 219 currentNetPlan.setLinksAndNodesFailureState(ev.linksToUp , ev.linksToDown , ev.nodesToUp , ev.nodesToDown); 220 routesFromDownToUp.removeAll(currentNetPlan.getRoutesDown()); 221 222 if (isProtectionRecovery) 223 { 224 /* POLICY WITH PROTECTION: */ 225 /* Primary up => backup carried traffic in no failure state is zero */ 226 /* Primary down => backup carried traffic in no failure state is equal to the carried in no failure of the primary */ 227 228 /* If primary GOES up => backup carried is set to zero */ 229 for (Route r : routesFromDownToUp) 230 if (r.hasBackupRoutes()) r.getBackupRoutes().get(0).setCarriedTraffic(0, null); // primary to up => carried in backup to zero 231 232 /* Now for the each primary route that is down, set backup carried to the one of the primary, but count recovered only if backup is up */ 233 for (Route r : new HashSet<Route> (currentNetPlan.getRoutesDown(layer))) 234 { 235 if (r.isBackupRoute()) continue; 236 this.stat_numAttemptedToRecoverConnections ++; 237 this.stat_trafficAttemptedToRecoverConnections += r.getCarriedTrafficInNoFailureState(); 238 if (r.hasBackupRoutes()) 239 { 240 final Route backupRoute = r.getBackupRoutes().get(0); 241 backupRoute.setCarriedTraffic(r.getCarriedTrafficInNoFailureState(), null); 242 if (!backupRoute.isDown()) 243 { 244 this.stat_numSuccessfullyRecoveredConnections ++; 245 this.stat_trafficSuccessfullyRecoveredConnections += r.getCarriedTrafficInNoFailureState(); 246 } 247 } 248 } 249 } 250 else if (isRestorationRecovery) 251 { 252 /* Try to reroute the routes that are still failing */ 253 for (Route r : new HashSet<Route> (currentNetPlan.getRoutesDown(layer))) 254 { 255 this.stat_numAttemptedToRecoverConnections ++; 256 this.stat_trafficAttemptedToRecoverConnections += r.getCarriedTrafficInNoFailureState(); 257 List<Link> spLinks = computeValidPathNewRoute (r.getDemand() , r.getOccupiedCapacityInNoFailureState()); 258 if (!spLinks.isEmpty()) 259 { 260 r.setSeqLinks(spLinks); 261 this.stat_numSuccessfullyRecoveredConnections ++; 262 this.stat_trafficSuccessfullyRecoveredConnections += r.getCarriedTrafficInNoFailureState(); 263 } 264 } 265 } 266 } 267 else throw new Net2PlanException ("Unknown event type: " + event); 268 } 269 @Override 270 public String finish(StringBuilder output, double simTime) 271 { 272 final double trafficBlockingOnConnectionSetup = stat_trafficOfferedConnections == 0? 0 : 1 - (stat_trafficCarriedConnections / stat_trafficOfferedConnections ); 273 final double connectionBlockingOnConnectionSetup = stat_numOfferedConnections == 0? 0.0 : 1 - (((double) stat_numCarriedConnections) / ((double) stat_numOfferedConnections)); 274 final double successProbabilityRecovery = stat_numAttemptedToRecoverConnections == 0? 0.0 : (((double) stat_numSuccessfullyRecoveredConnections) / ((double) stat_numAttemptedToRecoverConnections)); 275 final double successProbabilityTrafficRecovery = stat_trafficAttemptedToRecoverConnections == 0? 0.0 : (((double) stat_trafficSuccessfullyRecoveredConnections) / ((double) stat_trafficAttemptedToRecoverConnections)); 276 final double dataTime = simTime - stat_transitoryInitTime; 277 if (dataTime <= 0) { output.append ("<p>No time for data acquisition</p>"); return ""; } 278 279 output.append (String.format("<p>Total traffic of offered connections: number connections %d, total time average traffic: %f</p>", stat_numOfferedConnections, stat_trafficOfferedConnections / dataTime)); 280 output.append (String.format("<p>Total traffic of carried connections: number connections %d, total time average traffic: %f</p>", stat_numCarriedConnections, stat_trafficCarriedConnections / dataTime)); 281 output.append (String.format("<p>Blocking at connection setup: Probability of blocking a connection: %f, Fraction of blocked traffic: %f</p>", connectionBlockingOnConnectionSetup , trafficBlockingOnConnectionSetup)); 282 output.append (String.format("<p>Number connections attempted to recover: %d (summing time average traffic: %f). </p>", stat_numAttemptedToRecoverConnections, stat_trafficAttemptedToRecoverConnections / dataTime)); 283 output.append (String.format("<p>Number connections successfully recovered: %d (summing time average traffic: %f). </p>", stat_numSuccessfullyRecoveredConnections, stat_trafficSuccessfullyRecoveredConnections / dataTime)); 284 output.append (String.format("<p>Probability of successfully recover a connection: %f. Proability weighted by the connection traffic: %f). </p>", successProbabilityRecovery, successProbabilityTrafficRecovery)); 285// output.append (String.format("<p>Total traffic carried at current state: %f. </p>", -1); 286 return ""; 287 } 288 289 @Override 290 public void finishTransitory(double simTime) 291 { 292 this.stat_trafficOfferedConnections = 0; 293 this.stat_trafficCarriedConnections = 0; 294 this.stat_trafficAttemptedToRecoverConnections = 0; 295 this.stat_trafficSuccessfullyRecoveredConnections = 0; 296 this.stat_numOfferedConnections = 0; 297 this.stat_numCarriedConnections = 0; 298 this.stat_numAttemptedToRecoverConnections = 0; 299 this.stat_numSuccessfullyRecoveredConnections = 0; 300 this.stat_transitoryInitTime = simTime; 301 } 302 303 /* down links cannot be used */ 304 private List<Link> computeValidPathNewRoute (Demand demand , double occupiedLinkCapacity) 305 { 306 final List<List<Link>> paths = cpl.get(Pair.of(demand.getIngressNode() , demand.getEgressNode())); 307 /* If load sharing */ 308 if (isLoadSharing) 309 { 310 final int randomChosenIndex = rng.nextInt(paths.size()); 311 final List<Link> seqLinks = cpl.get(Pair.of(demand.getIngressNode() , demand.getEgressNode())).get(randomChosenIndex); 312 if (isValidPath(seqLinks, occupiedLinkCapacity).getFirst()) return seqLinks; else return new LinkedList<Link> (); 313 } 314 /* If alternate or LCR */ 315 List<Link> lcrSoFar = null; double lcrIdleCapacitySoFar = -Double.MAX_VALUE; 316 for (List<Link> seqLinks : paths) 317 { 318 Pair<Boolean,Double> isValid = isValidPath(seqLinks, occupiedLinkCapacity); 319 if (isValid.getFirst()) 320 { 321 if (isAlternateRouting) return seqLinks; 322 if (isValid.getSecond() > lcrIdleCapacitySoFar) 323 { 324 lcrIdleCapacitySoFar = isValid.getSecond(); 325 lcrSoFar = seqLinks; 326 } 327 } 328 } 329 return (isAlternateRouting || (lcrSoFar == null))? new LinkedList<Link> () : lcrSoFar; 330 } 331 332 private Pair<List<Link>,List<Link>> computeValid11PathPairNewRoute (Demand demand , double occupiedLinkCapacity) 333 { 334 final List<Pair<List<Link>,List<Link>>> pathPairs = cpl11.get(Pair.of(demand.getIngressNode() , demand.getEgressNode())); 335 /* If load sharing */ 336 if (isLoadSharing) 337 { 338 final int randomChosenIndex = pathPairs.isEmpty()? 0 : rng.nextInt(pathPairs.size()); 339 final Pair<List<Link>,List<Link>> pathPair = pathPairs.get(randomChosenIndex); 340 if (isValidPath(pathPair.getFirst(), occupiedLinkCapacity).getFirst() && isValidPath(pathPair.getSecond(), occupiedLinkCapacity).getFirst ()) 341 return pathPair; else return null; 342 } 343 344 Pair<List<Link>,List<Link>> lcrSoFar = null; double lcrIdleCapacitySoFar= -Double.MAX_VALUE; 345 for (Pair<List<Link>,List<Link>> pathPair : this.cpl11.get(Pair.of(demand.getIngressNode() , demand.getEgressNode()))) 346 { 347 Pair<Boolean,Double> validityFirstPath = isValidPath(pathPair.getFirst(), occupiedLinkCapacity); 348 if (!validityFirstPath.getFirst()) continue; 349 Pair<Boolean,Double> validitySecondPath = isValidPath(pathPair.getSecond(), occupiedLinkCapacity); 350 if (!validitySecondPath.getFirst()) continue; 351 if (isAlternateRouting) return pathPair; 352 final double thisPairIdleCapacity = Math.min(validityFirstPath.getSecond(), validitySecondPath.getSecond()); 353 if (thisPairIdleCapacity > lcrIdleCapacitySoFar) 354 { 355 lcrIdleCapacitySoFar = thisPairIdleCapacity; 356 lcrSoFar = pathPair; 357 } 358 } 359 return lcrSoFar; //if alternate, this is null also 360 } 361 362 private static Pair<Boolean,Double> isValidPath (List<Link> seqLinks , double routeOccupiedCapacity) 363 { 364 final double minimumCapacityNeeded = routeOccupiedCapacity; 365 boolean validPath = true; double thisPathIdleCapacity = Double.MAX_VALUE; 366 for (Link e : seqLinks) 367 { 368 final double thisLinkIdleCapacity = e.getCapacity() - e.getCarriedTraffic(); 369 thisPathIdleCapacity = Math.min(thisPathIdleCapacity, thisLinkIdleCapacity); 370 if (e.isDown() || e.getOriginNode().isDown() || e.getDestinationNode().isDown() || (thisLinkIdleCapacity <= minimumCapacityNeeded)) 371 { validPath = false; break; } 372 } 373 return Pair.of(validPath, thisPathIdleCapacity); 374 } 375}