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 *******************************************************************************/
011package com.net2plan.examples.general.offline.nfv;
012
013import cern.colt.matrix.tdouble.DoubleFactory1D;
014import cern.colt.matrix.tdouble.DoubleMatrix1D;
015import cern.colt.matrix.tdouble.DoubleMatrix2D;
016import com.jom.OptimizationProblem;
017import com.net2plan.interfaces.networkDesign.*;
018import com.net2plan.utils.Constants.RoutingType;
019import com.net2plan.utils.InputParameter;
020import com.net2plan.utils.Pair;
021import com.net2plan.utils.Triple;
022import org.apache.commons.lang3.StringUtils;
023
024import java.util.*;
025import java.util.stream.Collectors;
026
027/**
028 * Algorithm based on an ILP solving several variants of the service chain allocation problem in networks with nodes 
029 * equipped with IT resources (CPU, RAM, HD), and the possibility to instantiate user-defined virtualized network functions (VNFs).
030 * 
031 * <p>The demands in the input design are service chain requests. The design produces one or more routes (just one 
032 * if non-bifurcated routing option is used) from the demand input to the output nodes, traversing the resources of 
033 * the required types (e.g. firewall, NAT, ...). Resource types to traverse are user defined. Each resource type is associated 
034 * a cost, a capacity, and an amount of IT resources it consumes when instantiated (CPU, RAM, HD). The algorithms produces 
035 * a design where all the service chain requests are satisfied, traversing the appropriate user-defined resources so that no 
036 * resource is oversubscribed, and IT resources (CPU, HD, RAM) in the nodes are also not oversubscribed.</p>
037 * 
038 * <p>The algorithm solves an ILP based on a flow-path formulation, where for each demand, a maximum of {@code k} (user-defined 
039 * parameter) minimum cost service chains are enumerated, using a variant of the k-shortest path problem. 
040 * Each candidate service chain for a demand traverses the appropriate resources 
041 * in the appropriate order. The formulation optimally searches among all the options for all the demands, the best global solution. </p>
042 * 
043 * <p>The result is returned by instanting the appropriate Demand (service chain request), Route (service chain) and 
044 * Resource (virtualized functions, and It resources) objects in the output design.</p>
045 * <p>The details of the algorithm will be provided in a publication currently under elaboration.</p>
046 * 
047 * @net2plan.keywords JOM, NFV
048 * @net2plan.inputParameters 
049 * @author Pablo Pavon-Marino
050 */
051public class Offline_nfvPlacementILP_v1 implements IAlgorithm
052{
053        private InputParameter k = new InputParameter ("k", (int) 5 , "Maximum number of admissible service chain paths per demand" , 1 , Integer.MAX_VALUE);
054        private InputParameter shortestPathType = new InputParameter ("shortestPathType", "#select# hops km" , "Criteria to compute the shortest path. Valid values: 'hops' or 'km'");
055        private InputParameter nfvTypesInfo = new InputParameter ("nfvTypesInfo", "NAT 1 1 1 1 1 ; FW 1 1 1 1 1" , "Info of NFVs that could be placed, separated by ';'. Each NFV info has six space-separated parameters: 1) type, 2) cost (measured in same units as the cost of one BW unit in a link),  3) CPU use, 4) RAM use, 5) HD use, 6) capacity in same units as traffic");
056        private InputParameter nonBifurcatedRouting = new InputParameter ("nonBifurcatedRouting", false , "True if the routing is constrained to be non-bifurcated");
057        private InputParameter overideBaseResourcesInfo = new InputParameter ("overideBaseResourcesInfo", true , "If true, the current resources in tne input n2p are removed, and for each node aone CPU, RAM and HD resources are created, with the capacities defined in input parameter defaultCPU_RAM_HD_Capacities");
058        private InputParameter defaultCPU_RAM_HD_Capacities = new InputParameter ("defaultCPU_RAM_HD_Capacities", "100 100 100" , "THe default capacity values (space separated) of CPU, RAM, HD");
059        private InputParameter overideSequenceTraversedNFVs = new InputParameter ("overideSequenceTraversedNFVs", true , "If true, all demands will reset the sequence of NFVs to traverse, to this (NFV types in this param are ; separated)");
060        private InputParameter defaultSequenceNFVsToTraverse = new InputParameter ("defaultSequenceNFVsToTraverse", "FW NAT" , "The default sequence of NFVs that demands must traverse");
061        private InputParameter solverName = new InputParameter ("solverName", "#select# glpk ipopt xpress cplex", "The solver name to be used by JOM. GLPK and IPOPT are free, XPRESS and CPLEX commercial. GLPK, XPRESS and CPLEX solve linear problems w/w.o integer contraints. IPOPT is can solve nonlinear problems (if convex, returns global optimum), but cannot handle integer constraints");
062        private InputParameter solverLibraryName = new InputParameter ("solverLibraryName", "" , "The solver library full or relative path, to be used by JOM. Leave blank to use JOM default.");
063        private InputParameter maxSolverTimeInSeconds = new InputParameter ("maxSolverTimeInSeconds", (double) -1 , "Maximum time granted to the solver to solve the problem. If this time expires, the solver returns the best solution found so far (if a feasible solution is found)");
064
065        private InputParameter optimizationTarget = new InputParameter ("optimizationTarget", "#select# min-total-cost" , "Type of optimization target. Choose among (i) minimize the link BW plus the cost of instantiated resources (assumed measured in cost units equal to the cost of one link BW unit)");
066        private InputParameter maxLengthInKmPerSubpath = new InputParameter ("maxLengthInKmPerSubpath", (double) -1 , "Subpaths (parts of the path split by resources) longer than this in km are considered not admissible. A non-positive number means this limit does not exist");
067        private InputParameter maxNumHopsPerSubpath = new InputParameter ("maxNumHopsPerSubpath", (int) -1 , "Subpaths (parts of the path split by resources) longer than this in number of hops are considered not admissible. A non-positive number means this limit does not exist");
068        private InputParameter maxPropDelayInMsPerSubpath = new InputParameter ("maxPropDelayInMsPerSubpath", (double) -1 , "Subpaths (parts of the path split by resources) longer than this in propagation delay (in ms) are considered not admissible. A non-positive number means this limit does not exist");
069
070        @Override
071        public String executeAlgorithm(NetPlan netPlan, Map<String, String> algorithmParameters, Map<String, String> net2planParameters)
072        {
073                /* Initialize all InputParameter objects defined in this object (this uses Java reflection) */
074                InputParameter.initializeAllInputParameterFieldsOfObject(this, algorithmParameters);
075                if (solverName.getString ().equalsIgnoreCase("ipopt")) throw new Net2PlanException ("With IPOPT solver, the routing cannot be constrained to be non-bifurcated");
076                
077                /* Initialize variables */
078                final int E = netPlan.getNumberOfLinks();
079                final int D = netPlan.getNumberOfDemands();
080                final int N = netPlan.getNumberOfNodes();
081                final double PRECISION_FACTOR = Double.parseDouble(net2planParameters.get("precisionFactor"));
082                if (E == 0 || D == 0) throw new Net2PlanException("This algorithm requires a topology with links and a demand set");
083        
084                /* Remove all unicast routed traffic. Any multicast routed traffic is kept */
085                netPlan.removeAllUnicastRoutingInformation();
086        
087                /* Add all the k-shortest candidate routes to the netPlan object carrying no traffic */
088                final DoubleMatrix1D linkCostVector = shortestPathType.getString().equalsIgnoreCase("hops")? DoubleFactory1D.dense.make (E , 1.0) : netPlan.getVectorLinkLengthInKm();
089        
090                if (overideBaseResourcesInfo.getBoolean())
091                {
092                        final List<Double> cpuRamHdCap = Arrays.stream(StringUtils.split(defaultCPU_RAM_HD_Capacities.getString(), " ")).map(e -> Double.parseDouble(e)).collect (Collectors.toList());
093                        netPlan.removeAllResources();
094                        for (Node n : netPlan.getNodes())
095                        {
096                                netPlan.addResource("CPU", "", Optional.of(n), cpuRamHdCap.get(0), "", null, 0.0, null);
097                                netPlan.addResource("RAM", "", Optional.of(n), cpuRamHdCap.get(1), "", null, 0.0, null);
098                                netPlan.addResource("HD", "", Optional.of(n), cpuRamHdCap.get(2), "", null, 0.0, null);
099                        }
100                }
101                if (overideSequenceTraversedNFVs.getBoolean())
102                {
103                        final List<String> nfvsToTraverse = Arrays.stream(StringUtils.split(defaultSequenceNFVsToTraverse.getString() , " ")).collect(Collectors.toList());
104                        for (Demand d : netPlan.getDemands())
105                                d.setServiceChainSequenceOfTraversedResourceTypes(nfvsToTraverse);
106                }
107                
108                String [] nfvsInfoArray_f = StringUtils.split(nfvTypesInfo.getString() , ";");
109                final int NUMNFVTYPES = nfvsInfoArray_f.length;
110                List<String> nfvType_f = new ArrayList<String> ();
111                DoubleMatrix1D nfvCost_f = DoubleFactory1D.dense.make(NUMNFVTYPES);
112                DoubleMatrix1D nfvCpu_f = DoubleFactory1D.dense.make(NUMNFVTYPES);
113                DoubleMatrix1D nfvRam_f = DoubleFactory1D.dense.make(NUMNFVTYPES);
114                DoubleMatrix1D nfvHardDisk_f = DoubleFactory1D.dense.make(NUMNFVTYPES);
115                DoubleMatrix1D nfvCap_f = DoubleFactory1D.dense.make(NUMNFVTYPES);
116                for (String nfvInfo : nfvsInfoArray_f)
117                {
118                        final String [] fields = StringUtils.split(nfvInfo , " ");
119                        if (fields.length != 6) throw new Net2PlanException ("Wrong parameter format for NFV info");
120                        final String type = fields [0];
121                        if (nfvType_f.contains(type)) throw new Net2PlanException ("Wrong parameter format for NFV info: cannot repeat NFV types");
122                        final int index = nfvType_f.size();
123                        nfvType_f.add (type);
124                        nfvCost_f.set(index, Double.parseDouble(fields [1]));
125                        nfvCpu_f.set(index, Double.parseDouble(fields [2]));
126                        nfvRam_f.set(index, Double.parseDouble(fields [3]));
127                        nfvHardDisk_f.set(index, Double.parseDouble(fields [4]));
128                        nfvCap_f.set(index, Double.parseDouble(fields [5]));
129                }
130
131                DoubleMatrix1D cpu_n = DoubleFactory1D.dense.make(N);
132                DoubleMatrix1D ram_n = DoubleFactory1D.dense.make(N);
133                DoubleMatrix1D hardDisk_n = DoubleFactory1D.dense.make(N);
134                for (int index_n = 0; index_n < N ; index_n ++)
135                {
136                        final Node n = netPlan.getNode(index_n);
137                        Set<Resource> cpuResources = n.getResources("CPU"); if (cpuResources.size() > 1) throw new Net2PlanException ("A node cannot have more than one resource of type CPU, or RAM or HD");
138                        Set<Resource> ramResources = n.getResources("RAM"); if (ramResources.size() > 1) throw new Net2PlanException ("A node cannot have more than one resource of type CPU, or RAM or HD");
139                        Set<Resource> hardDiskResources = n.getResources("HD"); if (hardDiskResources.size() > 1) throw new Net2PlanException ("A node cannot have more than one resource of type CPU, or RAM or HD");
140                        cpu_n.set(index_n, cpuResources.iterator().next().getCapacity());
141                        ram_n.set(index_n, ramResources.iterator().next().getCapacity());
142                        hardDisk_n.set(index_n, hardDiskResources.iterator().next().getCapacity());
143                }
144
145                /* Instantiate "preliminary" NFV resources in the nodes, not consuming any base resource, and with a capacity as if one single instance existed. 
146                 * This is needed so service chain paths can be precomputed. 
147                 * If a node has not enough CPU/RAM/HD to even instantiate one single instance of a NFV type, do not add this resource in that node */
148                
149                for (Node n : netPlan.getNodes())
150                {
151                        final double cpuNode = cpu_n.get(n.getIndex());
152                        final double ramNode = ram_n.get(n.getIndex());
153                        final double hdNode = hardDisk_n.get(n.getIndex());
154                        for (int indexNFVType = 0 ; indexNFVType < NUMNFVTYPES ; indexNFVType ++)
155                        {
156                                if (nfvCpu_f.get(indexNFVType) > cpuNode) continue;
157                                if (nfvRam_f.get(indexNFVType)  > ramNode) continue;
158                                if (nfvHardDisk_f.get(indexNFVType)  > hdNode) continue;
159                                netPlan.addResource(nfvType_f.get(indexNFVType), "", Optional.of(n), nfvCap_f.get(indexNFVType), "", n.getResources("CPU", "RAM", "HD").stream().collect(Collectors.toMap(e -> e ,  e-> 0.0)) , 0, null);
160                        }
161                }
162                
163                /* Create the candidate service path list */
164                final Map<Demand,List<List<NetworkElement>>> cpl = netPlan.computeUnicastCandidateServiceChainList (linkCostVector, 
165                                null , k.getInt(), -1 , maxLengthInKmPerSubpath.getDouble(), maxNumHopsPerSubpath.getInt(), maxPropDelayInMsPerSubpath.getDouble());
166                for (Demand d : netPlan.getDemands())
167                {
168                        for (List<NetworkElement> path : cpl.get(d))
169                                netPlan.addServiceChain(d, 0, Collections.nCopies(path.size() , 0.0), path, null);
170                }
171                final int P = netPlan.getNumberOfRoutes(); 
172        
173                /* Create the optimization problem object (JOM library) */
174                OptimizationProblem op = new OptimizationProblem();
175        
176                /* Set some input parameters to the problem */
177                op.setInputParameter("u_e", netPlan.getVectorLinkSpareCapacity(), "row"); /* for each link, its unused capacity (the one not used by any mulitcast trees) */
178                op.setInputParameter("A_dp", netPlan.getMatrixDemand2RouteAssignment()); /* 1 in position (d,p) if demand d is served by path p, 0 otherwise */ 
179                op.setInputParameter("A_ep", netPlan.getMatrixLink2RouteAssignment()); /* 1 in position (e,p) if link e is traversed by path p, 0 otherwise */
180                op.setInputParameter("h_d", netPlan.getVectorDemandOfferedTraffic(), "row"); /* for each demand, its offered traffic */
181                op.setInputParameter("h_p", netPlan.getVectorRouteOfferedTrafficOfAssociatedDemand () , "row"); /* for each path, the offered traffic of its demand */
182                op.setInputParameter("c_f", nfvCost_f , "row"); /* for each NFV type, its cost */
183                op.setInputParameter("cpu_f", nfvCpu_f , "row"); /* for each NFV type, its CPU */
184                op.setInputParameter("ram_f", nfvRam_f , "row"); /* for each NFV type, its RAM */
185                op.setInputParameter("hardDisk_f", nfvHardDisk_f , "row"); /* for each NFV type, its HD */
186                op.setInputParameter("cap_f", nfvCap_f , "row"); /* for each NFV type, its capacity */
187                op.setInputParameter("cpu_n", cpu_n , "row"); /* for each node, CPU capacity  */
188                op.setInputParameter("ram_n", ram_n , "row"); /* for each node, RAM capacity  */
189                op.setInputParameter("hardDisk_n", hardDisk_n , "row"); /* for each node, HD capacity  */
190                
191                /* Write the problem formulations */
192                if (optimizationTarget.getString ().equals ("min-total-cost")) 
193                {
194                        op.setInputParameter("l_p", netPlan.getVectorRouteNumberOfLinks() , "row"); /* for each path, the number of traversed links */
195                        op.addDecisionVariable("xx_p", nonBifurcatedRouting.getBoolean() , new int[] { 1, P }, 0, 1); /* the FRACTION of traffic of demand d(p) that is carried by p */
196                        op.addDecisionVariable("y_nf", true , new int[] { N, NUMNFVTYPES }, 0, Integer.MAX_VALUE); /* the number of NFVs of each type that are instantiated */
197        
198                        op.setObjectiveFunction("minimize", "sum (l_p .* h_p .* xx_p) + sum (y_nf * c_f') "); 
199
200                        op.addConstraint("A_dp * xx_p' == 1"); /* for each demand, the 100% of the traffic is carried (summing the associated paths) */
201                        op.addConstraint("A_ep * (h_p .* xx_p)' <= u_e'"); /* the traffic in each link cannot exceed its capacity  */
202                        op.addConstraint("y_nf * cpu_f' <= cpu_n'"); /* the VFs instantiated in the node cannot consume more CPU than the node has */
203                        op.addConstraint("y_nf * ram_f' <= ram_n'"); /* the VFs instantiated in the node cannot consume more RAM than the node has */
204                        op.addConstraint("y_nf * hardDisk_f' <= hardDisk_n'"); /* the VFs instantiated in the node cannot consume more hard disk than the node has */
205                        
206                        for (int indexNFVType = 0 ; indexNFVType < NUMNFVTYPES ; indexNFVType ++)
207                        {
208                                final String type = nfvType_f.get(indexNFVType);
209                                Pair<List<Resource>,DoubleMatrix2D> info = netPlan.getMatrixResource2RouteAssignment(type); 
210                                op.setInputParameter("f", indexNFVType); /* K in position (res,p) if resource res is traversed by path p K times */
211                                op.setInputParameter("A_nfvp", info.getSecond()); /* K in position (res,p) if resource res is traversed by path p K times */
212                                op.setInputParameter("u_nfv", nfvCap_f.get(indexNFVType)); /* for each resource link, its unused capacity (the one not used by any mulitcast trees) */
213                                op.addConstraint("A_nfvp * (h_p .* xx_p)' <= y_nf(all,f) * u_nfv"); /* the traffic in each link cannot exceed its capacity  */
214                        }
215                }
216                else throw new Net2PlanException ("Unknown optimization target " + optimizationTarget.getString());
217        
218                
219                System.out.println ("solverLibraryName: " +  solverLibraryName.getString ());
220                op.solve(solverName.getString (), "solverLibraryName", solverLibraryName.getString () , "maxSolverTimeInSeconds" , maxSolverTimeInSeconds.getDouble ());
221                //op.solve(solverName.getString (), "maxSolverTimeInSeconds" , maxSolverTimeInSeconds.getDouble ());
222        
223                System.out.println ("solverLibraryName: " +  solverLibraryName.getString ());
224        
225                /* If no solution is found, quit */
226                if (op.feasibleSolutionDoesNotExist()) throw new Net2PlanException("The problem has no feasible solution");
227                if (!op.solutionIsFeasible()) throw new Net2PlanException("A feasible solution was not found");
228                
229                /* Save the solution found in the netPlan object */
230                final DoubleMatrix1D xx_p = op.getPrimalSolution("xx_p").view1D();
231                final DoubleMatrix2D y_nf = op.getPrimalSolution("y_nf").view2D();
232
233                for (Route r : netPlan.getRoutes())
234                {
235                        final double carriedTrafficAndOccupiedLinkCapacity = Math.max(0 , xx_p.get(r.getIndex())) * r.getDemand().getOfferedTraffic();
236                        r.setCarriedTraffic(carriedTrafficAndOccupiedLinkCapacity, carriedTrafficAndOccupiedLinkCapacity);
237                }
238
239                for (Node n : netPlan.getNodes())
240                {
241                        final Resource cpu = n.getResources("CPU").iterator().next(); 
242                        final Resource ram = n.getResources("RAM").iterator().next(); 
243                        final Resource hd = n.getResources("HD").iterator().next(); 
244                        for (int indexNFVType = 0 ; indexNFVType < NUMNFVTYPES ; indexNFVType ++)
245                        {
246                                final String type = nfvType_f.get(indexNFVType);
247                                if (n.getResources(type).isEmpty()) continue;
248                                final Resource nfv = n.getResources(type).iterator().next();
249                                final int this_ynf = (int) y_nf.get(n.getIndex(), indexNFVType);
250                                if (this_ynf == 0) { nfv.remove(); continue; }
251                                Map<Resource,Double> occupyInBaseResources = new HashMap<Resource,Double> ();
252                                occupyInBaseResources.put(cpu, this_ynf * nfvCpu_f.get(indexNFVType));
253                                occupyInBaseResources.put(ram, this_ynf * nfvRam_f.get(indexNFVType));
254                                occupyInBaseResources.put(hd, this_ynf * nfvHardDisk_f.get(indexNFVType));
255                                nfv.setCapacity(this_ynf * nfvCap_f.get(indexNFVType), occupyInBaseResources);
256                        }
257                }
258                
259                netPlan.removeAllRoutesUnused(PRECISION_FACTOR); // routes with zero traffic (or close to zero, with PRECISION_FACTOR tolerance)
260        
261                return "Ok!: The solution found is guaranteed to be optimal: " + op.solutionIsOptimal() + ". Number routes = " + netPlan.getNumberOfRoutes();
262        }
263        
264        @Override
265        public String getDescription()
266        {
267                return "Algorithm for NFV placement";
268        }
269        
270        
271        @Override
272        public List<Triple<String, String, String>> getParameters()
273        {
274                /* Returns the parameter information for all the InputParameter objects defined in this object (uses Java reflection) */
275                return InputParameter.getInformationAllInputParameterFieldsOfObject(this);
276        }
277
278}