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}