diff --git a/src/main/java/net/floodlightcontroller/routing/IRoutingService.java b/src/main/java/net/floodlightcontroller/routing/IRoutingService.java index a2af40ba7745c1f48de3656ff4199409a2f6c5ab..856ec0bda2267604688621387742e211d090793d 100644 --- a/src/main/java/net/floodlightcontroller/routing/IRoutingService.java +++ b/src/main/java/net/floodlightcontroller/routing/IRoutingService.java @@ -173,4 +173,20 @@ public interface IRoutingService extends IFloodlightService { * @return list of paths ordered least to greatest cost */ public List<Path> getPathsSlow(DatapathId src, DatapathId dst, int numReqPaths); + + /** + * Recompute paths now, regardless of whether or not there was a change in the + * topology. This should be called if {@link #setPathMetric(PATH_METRIC)} was + * invoked to change the PATH_METRIC **and we want the new metric to take effect + * now** for future getPathsFast() or getPath() function calls. This will allow other + * modules using IRoutingService path-finding to use paths based on the new metric + * if the other modules only use the "fast" path-finding API. + * + * One can use {@link IRoutingService#getPathsSlow(DatapathId, DatapathId, int)} if there is no + * urgency for the new metric to take effect and yet one would still like to see + * the paths using the new metric once or so. In this case, one need not invoke {@link #forceRecompute()}. + * + * @return true upon success; false otherwise + */ + public boolean forceRecompute(); } diff --git a/src/main/java/net/floodlightcontroller/routing/RoutingManager.java b/src/main/java/net/floodlightcontroller/routing/RoutingManager.java index 8268cee5395794637ac79b61d52d63913bac88d4..3110da7470d76371afd37d53c9350ac3def3b36f 100644 --- a/src/main/java/net/floodlightcontroller/routing/RoutingManager.java +++ b/src/main/java/net/floodlightcontroller/routing/RoutingManager.java @@ -122,6 +122,11 @@ public class RoutingManager implements IFloodlightModule, IRoutingService { public boolean pathExists(DatapathId src, DatapathId dst) { return tm.getCurrentTopologyInstance().pathExists(src, dst); } + + @Override + public boolean forceRecompute() { + return tm.forceRecompute(); + } /** * Registers an IRoutingDecisionChangedListener. @@ -157,5 +162,4 @@ public class RoutingManager implements IFloodlightModule, IRoutingService { listener.routingDecisionChanged(changedDecisions); } } - } \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/routing/web/ForceRecomputeResource.java b/src/main/java/net/floodlightcontroller/routing/web/ForceRecomputeResource.java new file mode 100644 index 0000000000000000000000000000000000000000..ce408644d53a60fb29ee78b2f63f126ffe103089 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/routing/web/ForceRecomputeResource.java @@ -0,0 +1,44 @@ +/** + * Copyright 2013, Big Switch Networks, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + **/ + +package net.floodlightcontroller.routing.web; + +import net.floodlightcontroller.routing.IRoutingService; +import org.restlet.resource.Post; +import org.restlet.resource.Put; +import org.restlet.resource.ServerResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public class ForceRecomputeResource extends ServerResource { + private static final Logger log = LoggerFactory.getLogger(ForceRecomputeResource.class); + + @Put + @Post + public Map<String, String> forceRecompute() { + IRoutingService routing = + (IRoutingService)getContext().getAttributes(). + get(IRoutingService.class.getCanonicalName()); + + boolean result = routing.forceRecompute(); + log.debug("Force recompute result {}", result); + return ImmutableMap.of("result", result ? "paths recomputed" : "error recomputing paths"); + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/routing/web/MaxFastPathsResource.java b/src/main/java/net/floodlightcontroller/routing/web/MaxFastPathsResource.java new file mode 100644 index 0000000000000000000000000000000000000000..1c0b193425827314a3652110388cf36f4f9ffd92 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/routing/web/MaxFastPathsResource.java @@ -0,0 +1,109 @@ +/** + * Copyright 2013, Big Switch Networks, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + **/ + +package net.floodlightcontroller.routing.web; + +import net.floodlightcontroller.routing.IRoutingService; + +import org.restlet.resource.Get; +import org.restlet.resource.Post; +import org.restlet.resource.Put; +import org.restlet.resource.ServerResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.MappingJsonFactory; +import com.google.common.collect.ImmutableMap; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +public class MaxFastPathsResource extends ServerResource { + private static final Logger log = LoggerFactory.getLogger(MaxFastPathsResource.class); + + private static String maxPathsFromJson(String json) { + MappingJsonFactory f = new MappingJsonFactory(); + JsonParser jp; + String max = ""; + try { + try { + jp = f.createParser(json); + } catch (JsonParseException e) { + throw new IOException(e); + } + + jp.nextToken(); + if (jp.getCurrentToken() != JsonToken.START_OBJECT) { + throw new IOException("Expected START_OBJECT"); + } + + while (jp.nextToken() != JsonToken.END_OBJECT) { + if (jp.getCurrentToken() != JsonToken.FIELD_NAME) { + throw new IOException("Expected FIELD_NAME"); + } + + String n = jp.getCurrentName(); + jp.nextToken(); + if (jp.getText().equals("")) { + continue; + } + + if (n.equalsIgnoreCase("max_fast_paths")) { + max = jp.getText(); + } + } + } catch (IOException e) { + log.error("Unable to parse JSON string: {}", e); + } + return max.trim().toLowerCase(); + } + + @Put + @Post + public Map<String, String> changeMaxPathsToCompute(String json) { + IRoutingService routing = + (IRoutingService)getContext().getAttributes(). + get(IRoutingService.class.getCanonicalName()); + + int max = 0; + + try { + max = Integer.parseInt(maxPathsFromJson(json)); + if (max < 0) { + throw new NumberFormatException(); + } + } catch (NumberFormatException e) { + log.error("Could not parse max_fast_paths {}", max); + return Collections.singletonMap("error", "invalid max_fast_paths: " + max); + } + + log.debug("Setting max_fast_paths to {}", max); + routing.setMaxPathsToCompute(max); + return ImmutableMap.of("max_fast_paths", Integer.toString(routing.getMaxPathsToCompute())); + } + + @Get + public Map<String, String> getMaxPaths() { + IRoutingService routing = + (IRoutingService)getContext().getAttributes(). + get(IRoutingService.class.getCanonicalName()); + return ImmutableMap.of("max_fast_paths", Integer.toString(routing.getMaxPathsToCompute())); + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/routing/web/PathMetricsResource.java b/src/main/java/net/floodlightcontroller/routing/web/PathMetricsResource.java index cf032a135520f0395eefa7a405409f673af901d8..c9c51380abac5bdac6ada7545cdbea7fdd34bb6c 100644 --- a/src/main/java/net/floodlightcontroller/routing/web/PathMetricsResource.java +++ b/src/main/java/net/floodlightcontroller/routing/web/PathMetricsResource.java @@ -18,53 +18,101 @@ package net.floodlightcontroller.routing.web; import net.floodlightcontroller.routing.IRoutingService; import net.floodlightcontroller.routing.IRoutingService.PATH_METRIC; + +import org.restlet.resource.Get; import org.restlet.resource.Post; import org.restlet.resource.Put; import org.restlet.resource.ServerResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.MappingJsonFactory; +import com.google.common.collect.ImmutableMap; + +import java.io.IOException; import java.util.Collections; import java.util.Map; public class PathMetricsResource extends ServerResource { private static final Logger log = LoggerFactory.getLogger(PathMetricsResource.class); + private static String metricFromJson(String json) { + MappingJsonFactory f = new MappingJsonFactory(); + JsonParser jp; + String metric = ""; + try { + try { + jp = f.createParser(json); + } catch (JsonParseException e) { + throw new IOException(e); + } + + jp.nextToken(); + if (jp.getCurrentToken() != JsonToken.START_OBJECT) { + throw new IOException("Expected START_OBJECT"); + } + + while (jp.nextToken() != JsonToken.END_OBJECT) { + if (jp.getCurrentToken() != JsonToken.FIELD_NAME) { + throw new IOException("Expected FIELD_NAME"); + } + + String n = jp.getCurrentName(); + jp.nextToken(); + if (jp.getText().equals("")) { + continue; + } + + if (n.equalsIgnoreCase("metric")) { + metric = jp.getText(); + } + } + } catch (IOException e) { + log.error("Unable to parse JSON string: {}", e); + } + return metric.trim().toLowerCase(); + } + @Put @Post - public Map<String, String> changeMetric() { + public Map<String, String> changeMetric(String json) { IRoutingService routing = (IRoutingService)getContext().getAttributes(). - get(IRoutingService.class.getCanonicalName()); + get(IRoutingService.class.getCanonicalName()); - String metric = (String) getRequestAttributes().get("metric"); - metric = metric.trim().toLowerCase(); + String metric = metricFromJson(json); PATH_METRIC type; - switch (metric) { - case "latency": - type = PATH_METRIC.LATENCY; - break; - case "utilization": - type = PATH_METRIC.UTILIZATION; - break; - case "hopcount": - type = PATH_METRIC.HOPCOUNT; - break; - case "hopcount_avoid_tunnels": - type = PATH_METRIC.HOPCOUNT_AVOID_TUNNELS; - break; - case "link_speed": - type = PATH_METRIC.LINK_SPEED; - break; - default: - log.error("Invalid input {}", metric); - return Collections.singletonMap("error", "invalid path metric " + metric); + if (metric.equals(PATH_METRIC.LATENCY.getMetricName())) { + type = PATH_METRIC.LATENCY; + } else if (metric.equals(PATH_METRIC.UTILIZATION.getMetricName())) { + type = PATH_METRIC.UTILIZATION; + } else if (metric.equals(PATH_METRIC.HOPCOUNT.getMetricName())) { + type = PATH_METRIC.HOPCOUNT; + } else if (metric.equals(PATH_METRIC.HOPCOUNT_AVOID_TUNNELS.getMetricName())) { + type = PATH_METRIC.HOPCOUNT_AVOID_TUNNELS; + } else if (metric.equals(PATH_METRIC.LINK_SPEED.getMetricName())) { + type = PATH_METRIC.LINK_SPEED; + } else { + log.error("Invalid input {}", metric); + return Collections.singletonMap("error", "invalid path metric: " + metric); } log.debug("Setting path metric to {}", type.getMetricName()); routing.setPathMetric(type); - return Collections.singletonMap("success", "path metric set to " + type.getMetricName()); + return Collections.singletonMap("metric", type.getMetricName()); + } + + @Get + public Map<String, String> getMetric() { + IRoutingService routing = + (IRoutingService)getContext().getAttributes(). + get(IRoutingService.class.getCanonicalName()); + PATH_METRIC metric = routing.getPathMetric(); + return ImmutableMap.of("metric", metric.getMetricName()); } } \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/routing/web/RoutingWebRoutable.java b/src/main/java/net/floodlightcontroller/routing/web/RoutingWebRoutable.java index d9901f0fbb82a133030703847bddb88d60708345..b56a2a5b6f5f407b056284e997e89b8fad5b300e 100644 --- a/src/main/java/net/floodlightcontroller/routing/web/RoutingWebRoutable.java +++ b/src/main/java/net/floodlightcontroller/routing/web/RoutingWebRoutable.java @@ -35,8 +35,9 @@ public class RoutingWebRoutable implements RestletRoutable { router.attach("/paths/{src-dpid}/{dst-dpid}/{num-paths}/json", PathsResource.class); router.attach("/paths/fast/{src-dpid}/{dst-dpid}/{num-paths}/json", PathsResource.class); router.attach("/paths/slow/{src-dpid}/{dst-dpid}/{num-paths}/json", PathsResource.class); - router.attach("/setpathmetric/{metric}/json", PathMetricsResource.class); - + router.attach("/metric/json", PathMetricsResource.class); + router.attach("/paths/force-recompute/json", ForceRecomputeResource.class); + router.attach("/paths/max-fast-paths/json", MaxFastPathsResource.class); return router; } diff --git a/src/main/java/net/floodlightcontroller/topology/ITopologyManagerBackend.java b/src/main/java/net/floodlightcontroller/topology/ITopologyManagerBackend.java index 64c4e06a2abcf3581fa23e5a99a96503d92d727d..542a7728e4b42c2291ab8d8d36cce1b7abe69270 100644 --- a/src/main/java/net/floodlightcontroller/topology/ITopologyManagerBackend.java +++ b/src/main/java/net/floodlightcontroller/topology/ITopologyManagerBackend.java @@ -2,7 +2,7 @@ package net.floodlightcontroller.topology; import net.floodlightcontroller.routing.IRoutingService.PATH_METRIC; -public interface ITopologyManagerBackend { +public interface ITopologyManagerBackend extends ITopologyService { public TopologyInstance getCurrentTopologyInstance(); public PATH_METRIC getPathMetric(); @@ -10,4 +10,6 @@ public interface ITopologyManagerBackend { public int getMaxPathsToCompute(); public void setMaxPathsToCompute(int max); + + public boolean forceRecompute(); } \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/topology/ITopologyPathFinding.java b/src/main/java/net/floodlightcontroller/topology/ITopologyPathFinding.java new file mode 100644 index 0000000000000000000000000000000000000000..3260f460650910f2331a6a1ff0095fc15e23efc4 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/topology/ITopologyPathFinding.java @@ -0,0 +1,7 @@ +package net.floodlightcontroller.topology; + +import net.floodlightcontroller.routing.IRoutingService; + +public interface ITopologyPathFinding extends IRoutingService { + +} diff --git a/src/main/java/net/floodlightcontroller/topology/TopologyManager.java b/src/main/java/net/floodlightcontroller/topology/TopologyManager.java index ffeb928fda25e0826ee39b0ee0576026734901fe..67200db0e9ab4d46725776842d7b7ce1772bf952 100644 --- a/src/main/java/net/floodlightcontroller/topology/TopologyManager.java +++ b/src/main/java/net/floodlightcontroller/topology/TopologyManager.java @@ -62,16 +62,15 @@ import java.util.concurrent.TimeUnit; * through the topology. */ public class TopologyManager implements IFloodlightModule, ITopologyService, - ITopologyManagerBackend, ILinkDiscoveryListener, IOFMessageListener { - - protected static Logger log = LoggerFactory.getLogger(TopologyManager.class); +ITopologyManagerBackend, ILinkDiscoveryListener, IOFMessageListener { + private static Logger log = LoggerFactory.getLogger(TopologyManager.class); public static final String MODULE_NAME = "topology"; protected static IStatisticsService statisticsService; protected static volatile PATH_METRIC pathMetric = PATH_METRIC.HOPCOUNT_AVOID_TUNNELS; //default: compute paths on hop count protected static boolean collectStatistics = false; - + /** * Maximum number of route entries stored in memory. */ @@ -177,8 +176,8 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, @Override public void run() { try { - if (ldUpdates.peek() != null) { - updateTopology(); + if (ldUpdates.peek() != null) { /* must check here, otherwise will run every interval */ + updateTopology("link-discovery-updates", false); } handleMiscellaneousPeriodicEvents(); } @@ -197,13 +196,17 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, return; } - public boolean updateTopology() { + public synchronized boolean updateTopology(String reason, boolean forced) { boolean newInstanceFlag; linksUpdated = false; dtLinksUpdated = false; tunnelPortsUpdated = false; - List<LDUpdate> appliedUpdates = applyUpdates(); - newInstanceFlag = createNewInstance("link-discovery-updates"); + List<LDUpdate> appliedUpdates = null; + if (this.ldUpdates.peek() != null) { + appliedUpdates = applyUpdates(); + } + log.info("Recomputing topology due to: {}", reason); + newInstanceFlag = createNewInstance(reason, forced); lastUpdateTime = new Date(); informListeners(appliedUpdates); return newInstanceFlag; @@ -621,22 +624,23 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, pathMetric = PATH_METRIC.LINK_SPEED; break; default: - log.error("Invalid routing metric {}. Using default {}", metric, pathMetric.getMetricName()); + log.error("Invalid routing metric {}. Using default {}", + metric, pathMetric.getMetricName()); break; } } log.info("Path metrics set to {}", pathMetric); - String maxroutes = configOptions.get("maxPathsToCompute") != null + String maxroutes = configOptions.get("maxPathsToCompute") != null ? configOptions.get("maxPathsToCompute").trim() : null; - if (maxroutes != null) { - try { - maxPathsToCompute = Integer.parseInt(maxroutes); - } catch (NumberFormatException e) { - log.error("Invalid 'maxPathsToCompute'. Using default {}", maxPathsToCompute); - } - } - log.info("Will compute a max of {} paths upon topology updates", maxPathsToCompute); + if (maxroutes != null) { + try { + maxPathsToCompute = Integer.parseInt(maxroutes); + } catch (NumberFormatException e) { + log.error("Invalid 'maxPathsToCompute'. Using default {}", maxPathsToCompute); + } + } + log.info("Will compute a max of {} paths upon topology updates", maxPathsToCompute); } @Override @@ -932,7 +936,7 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, } public boolean createNewInstance() { - return createNewInstance("internal"); + return createNewInstance("internal", false); } /** @@ -941,10 +945,12 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, * and tunnel ports. The method returns if a new instance of * topology was created or not. */ - protected boolean createNewInstance(String reason) { + protected boolean createNewInstance(String reason, boolean forced) { Set<NodePortTuple> blockedPorts = new HashSet<NodePortTuple>(); - if (!linksUpdated) return false; + if (!linksUpdated && !forced) { + return false; + } Map<NodePortTuple, Set<Link>> openflowLinks; openflowLinks = @@ -1064,9 +1070,12 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, public void informListeners(List<LDUpdate> linkUpdates) { - - if (role != null && role != HARole.ACTIVE) + if (linkUpdates == null || linkUpdates.isEmpty()) { + return; + } + if (role != null && role != HARole.ACTIVE) { return; + } for(int i=0; i < topologyAware.size(); ++i) { ITopologyListener listener = topologyAware.get(i); @@ -1248,7 +1257,7 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, linksUpdated = true; dtLinksUpdated = true; tunnelPortsUpdated = true; - createNewInstance("startup"); + createNewInstance("startup", false); lastUpdateTime = new Date(); } @@ -1320,4 +1329,10 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, public TopologyInstance getCurrentTopologyInstance() { return getCurrentInstance(); } + + @Override + public boolean forceRecompute() { + /* cannot invoke scheduled executor, since the update might not occur */ + return updateTopology("forced-recomputation", true); + } } \ No newline at end of file