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}