| package net.onrc.onos.core.intent.runtime; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import net.floodlightcontroller.util.MACAddress; |
| import net.onrc.onos.core.intent.FlowEntry; |
| import net.onrc.onos.core.intent.Intent; |
| import net.onrc.onos.core.intent.IntentOperation; |
| import net.onrc.onos.core.intent.IntentOperation.Operator; |
| import net.onrc.onos.core.intent.IntentOperationList; |
| import net.onrc.onos.core.intent.PathIntent; |
| import net.onrc.onos.core.intent.ShortestPathIntent; |
| import net.onrc.onos.core.topology.LinkData; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * The PlanCalcRuntime class receives a list of intents and operations (IntentOperationList), |
| * and does the following: |
| * <ul> |
| * <li> Convert the Intent into FlowEntries. |
| * <li> Determine the dependency between FlowEntries. |
| * <li> Create sets of FlowEntries to be installed by the OpenFlow protocol. |
| * </ul> |
| */ |
| |
| public class PlanCalcRuntime { |
| |
| private static final Logger log = LoggerFactory.getLogger(PlanCalcRuntime.class); |
| |
| /** |
| * The method produces a plan from a list of IntentOperations. |
| * <p> |
| * In this context, a plan is a list of sets of flow entries. Flow entries within |
| * a set can be installed in parallel. Each set of flow entries is dependent on |
| * all previous sets in the list. |
| * |
| * @param intentOps list of IntentOperations (Intent + Operation) for plan computation |
| * @return plan (list of set of flow entries) |
| */ |
| public List<Set<FlowEntry>> computePlan(IntentOperationList intentOps) { |
| long start = System.nanoTime(); |
| List<Collection<FlowEntry>> flowEntries = computeFlowEntries(intentOps); |
| long step1 = System.nanoTime(); |
| List<Set<FlowEntry>> plan = buildPhases(flowEntries); |
| long step2 = System.nanoTime(); |
| log.debug("MEASUREMENT: Compute flow entries: {} ns, Build phases: {} ns", |
| (step1 - start), (step2 - step1)); |
| return plan; |
| } |
| |
| /** |
| * Converts PathIntents to FlowEntries so they can be installed. |
| * <p> |
| * Note: This method only supports ShortestPathIntents at the moment. |
| * |
| * @param intentOps list of IntentOperations |
| * @return a list of FlowEntry objects for each Intent in intentOps |
| */ |
| private List<Collection<FlowEntry>> computeFlowEntries(IntentOperationList intentOps) { |
| List<Collection<FlowEntry>> flowEntries = new LinkedList<>(); |
| for (IntentOperation i : intentOps) { |
| if (!(i.intent instanceof PathIntent)) { |
| log.warn("Not a path intent: {}", i); |
| continue; |
| } |
| PathIntent intent = (PathIntent) i.intent; |
| Intent parent = intent.getParentIntent(); |
| long srcPort, dstPort; |
| long lastDstSw = -1, lastDstPort = -1, firstSrcSw = -1; |
| MACAddress srcMac, dstMac; |
| int idleTimeout = 0, hardTimeout = 0, firstSwitchIdleTimeout = 0, firstSwitchHardTimeout = 0; |
| Long cookieId = null; |
| int srcIP, dstIP; |
| if (parent instanceof ShortestPathIntent) { |
| ShortestPathIntent pathIntent = (ShortestPathIntent) parent; |
| srcPort = pathIntent.getSrcPortNumber(); |
| |
| if (pathIntent.getSrcMac() != ShortestPathIntent.EMPTYMACADDRESS) { |
| srcMac = MACAddress.valueOf(pathIntent.getSrcMac()); |
| } else { |
| srcMac = null; |
| } |
| |
| if (pathIntent.getDstMac() != ShortestPathIntent.EMPTYMACADDRESS) { |
| dstMac = MACAddress.valueOf(pathIntent.getDstMac()); |
| } else { |
| dstMac = null; |
| } |
| |
| srcIP = pathIntent.getSrcIp(); |
| dstIP = pathIntent.getDstIp(); |
| |
| lastDstSw = pathIntent.getDstSwitchDpid(); |
| firstSrcSw = pathIntent.getSrcSwitchDpid(); |
| lastDstPort = pathIntent.getDstPortNumber(); |
| idleTimeout = pathIntent.getIdleTimeout(); |
| hardTimeout = pathIntent.getHardTimeout(); |
| firstSwitchIdleTimeout = pathIntent.getFirstSwitchIdleTimeout(); |
| firstSwitchHardTimeout = pathIntent.getFirstSwitchHardTimeout(); |
| try { |
| cookieId = Long.valueOf(pathIntent.getId()); |
| } catch (NumberFormatException e) { |
| log.trace("NumberFormatException : ", e); |
| } |
| } else { |
| log.warn("Unsupported Intent: {}", parent); |
| continue; |
| } |
| List<FlowEntry> entries = new ArrayList<>(); |
| for (LinkData linkData : intent.getPath()) { |
| long sw = linkData.getSrc().getDpid().value(); |
| dstPort = linkData.getSrc().getPortNumber().value(); |
| FlowEntry fe = new FlowEntry(sw, srcPort, dstPort, srcMac, dstMac, |
| srcIP, dstIP, i.operator); |
| if (sw != firstSrcSw) { |
| fe.setIdleTimeout(idleTimeout); |
| fe.setHardTimeout(hardTimeout); |
| } else { |
| fe.setIdleTimeout(firstSwitchIdleTimeout); |
| fe.setHardTimeout(firstSwitchHardTimeout); |
| } |
| if (cookieId != null) { |
| log.trace("cookieId is set: {}", cookieId); |
| fe.setFlowEntryId(cookieId); |
| } |
| entries.add(fe); |
| srcPort = linkData.getDst().getPortNumber().value(); |
| } |
| if (lastDstSw >= 0 && lastDstPort >= 0) { |
| long sw = lastDstSw; |
| dstPort = lastDstPort; |
| FlowEntry fe = new FlowEntry(sw, srcPort, dstPort, srcMac, dstMac, |
| srcIP, dstIP, i.operator); |
| if (cookieId != null) { |
| log.trace("cookieId is set: {}", cookieId); |
| fe.setFlowEntryId(cookieId); |
| } |
| if (sw != firstSrcSw) { |
| fe.setIdleTimeout(idleTimeout); |
| fe.setHardTimeout(hardTimeout); |
| } else { |
| fe.setIdleTimeout(firstSwitchIdleTimeout); |
| fe.setHardTimeout(firstSwitchHardTimeout); |
| } |
| entries.add(fe); |
| } |
| // reverse order of flow entries so they are installed backwards |
| Collections.reverse(entries); |
| flowEntries.add(entries); |
| } |
| return flowEntries; |
| } |
| |
| /** |
| * TODO |
| * |
| * Note: This method is for a testing purpose. Please leave it right now. |
| * |
| * @param flowEntries |
| * @return a list of phases, where each phase is a set of FlowEntries |
| */ |
| @SuppressWarnings("unused") |
| private List<Set<FlowEntry>> simpleBuildPhases(List<Collection<FlowEntry>> flowEntries) { |
| List<Set<FlowEntry>> plan = new ArrayList<>(); |
| Set<FlowEntry> phase = new HashSet<>(); |
| for (Collection<FlowEntry> c : flowEntries) { |
| phase.addAll(c); |
| } |
| plan.add(phase); |
| |
| return plan; |
| } |
| |
| /** |
| * Merges the lists generated by computeFlowEntries() into install phases. |
| * <p> |
| * This function will also remove duplicate entries. |
| * |
| * @param flowEntries list of lists of flowEntries |
| * @return a list of sets of FlowEntries to be installed |
| */ |
| private List<Set<FlowEntry>> buildPhases(List<Collection<FlowEntry>> flowEntries) { |
| Map<FlowEntry, Integer> map = new HashMap<>(); |
| List<Set<FlowEntry>> plan = new ArrayList<>(); |
| // merge equal FlowEntries |
| //TODO: explore ways to merge FlowEntries that contain the same match condition |
| for (Collection<FlowEntry> c : flowEntries) { |
| for (FlowEntry e : c) { |
| Integer i = map.get(e); |
| if (i == null) { |
| i = Integer.valueOf(0); |
| } |
| switch (e.getOperator()) { |
| case ADD: |
| i += 1; |
| break; |
| case REMOVE: |
| i -= 1; |
| break; |
| default: |
| break; |
| } |
| map.put(e, i); |
| } |
| } |
| |
| // really simple first iteration of plan |
| //TODO: optimize the map in phases |
| Set<FlowEntry> phase = new HashSet<>(); |
| for (Map.Entry<FlowEntry, Integer> entry : map.entrySet()) { |
| FlowEntry e = entry.getKey(); |
| Integer i = entry.getValue(); |
| if (i == 0) { |
| continue; |
| } else if (i > 0) { |
| e.setOperator(Operator.ADD); |
| } else if (i < 0) { |
| e.setOperator(Operator.REMOVE); |
| } |
| phase.add(e); |
| } |
| plan.add(phase); |
| |
| return plan; |
| } |
| } |