From FlowRule to static routes for Juniper
Conversion of FlowRules into static routes and retrieve of
installed static routes as FlowRules.
Change-Id: If960e4d15e431ae8f5ea75eff83913056a51c852
diff --git a/drivers/juniper/src/main/java/org/onosproject/drivers/juniper/FlowRuleJuniperImpl.java b/drivers/juniper/src/main/java/org/onosproject/drivers/juniper/FlowRuleJuniperImpl.java
new file mode 100644
index 0000000..ae9d7e9
--- /dev/null
+++ b/drivers/juniper/src/main/java/org/onosproject/drivers/juniper/FlowRuleJuniperImpl.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * 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 org.onosproject.drivers.juniper;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Strings;
+import org.apache.commons.lang.StringUtils;
+import org.onlab.packet.Ip4Address;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleProgrammable;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.netconf.DatastoreId;
+import org.onosproject.netconf.NetconfController;
+import org.onosproject.netconf.NetconfException;
+import org.onosproject.netconf.NetconfSession;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.drivers.juniper.JuniperUtils.OperationType;
+import static org.onosproject.drivers.juniper.JuniperUtils.OperationType.ADD;
+import static org.onosproject.drivers.juniper.JuniperUtils.OperationType.REMOVE;
+import static org.onosproject.drivers.juniper.JuniperUtils.commitBuilder;
+import static org.onosproject.drivers.juniper.JuniperUtils.rollbackBuilder;
+import static org.onosproject.drivers.juniper.JuniperUtils.routeAddBuilder;
+import static org.onosproject.drivers.juniper.JuniperUtils.routeDeleteBuilder;
+import static org.onosproject.drivers.utilities.XmlConfigParser.loadXmlString;
+import static org.onosproject.net.flow.FlowEntry.FlowEntryState.PENDING_REMOVE;
+import static org.onosproject.net.flow.FlowEntry.FlowEntryState.REMOVED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Conversion of FlowRules into static routes and retrieve of installed
+ * static routes as FlowRules.
+ * The selector of the FlowRule must contains the IPv4 address
+ * {@link org.onosproject.net.flow.TrafficSelector.Builder#matchIPDst(org.onlab.packet.IpPrefix)}
+ * of the host to connect and the treatment must include an
+ * output port {@link org.onosproject.net.flow.TrafficTreatment.Builder#setOutput(PortNumber)}
+ * All other instructions in the selector and treatment are ignored.
+ * <p>
+ * This implementation requires an IP adjacency
+ * (e.g., IP link discovered by {@link LinkDiscoveryJuniperImpl}) between the routers
+ * to find the next hop IP address.
+ */
+@Beta
+public class FlowRuleJuniperImpl extends AbstractHandlerBehaviour
+ implements FlowRuleProgrammable {
+
+ private static final String OK = "<ok/>";
+ public static final String IP_STRING = "ip";
+ private final org.slf4j.Logger log = getLogger(getClass());
+
+ @Override
+ public Collection<FlowEntry> getFlowEntries() {
+
+ DeviceId devId = checkNotNull(this.data().deviceId());
+ NetconfController controller = checkNotNull(handler().get(NetconfController.class));
+ NetconfSession session = controller.getDevicesMap().get(devId).getSession();
+ if (session == null) {
+ log.warn("Device {} is not registered in netconf", devId);
+ return Collections.EMPTY_LIST;
+ }
+
+ //Installed static routes
+ String reply;
+ try {
+ reply = session.get(routingTableBuilder());
+ } catch (IOException e) {
+ throw new RuntimeException(new NetconfException("Failed to retrieve configuration.",
+ e));
+ }
+ Collection<StaticRoute> devicesStaticRoutes =
+ JuniperUtils.parseRoutingTable(loadXmlString(reply));
+
+ //Expected FlowEntries installed
+ FlowRuleService flowRuleService = this.handler().get(FlowRuleService.class);
+ Iterable<FlowEntry> flowEntries = flowRuleService.getFlowEntries(devId);
+
+ Collection<FlowEntry> installedRules = new HashSet<>();
+ flowEntries.forEach(flowEntry -> {
+ Optional<IPCriterion> ipCriterion = getIpCriterion(flowEntry);
+ if (!ipCriterion.isPresent()) {
+ return;
+ }
+
+ Optional<OutputInstruction> output = getOutput(flowEntry);
+ if (!output.isPresent()) {
+ return;
+ }
+ //convert FlowRule into static route
+ getStaticRoute(devId, ipCriterion.get(), output.get(), flowEntry.priority()).ifPresent(staticRoute -> {
+ //Two type of FlowRules:
+ //1. FlowRules to forward to a remote subnet: they are translated into static route
+ // configuration. So a removal request will be processed.
+ //2. FlowRules to forward on a subnet directly attached to the router (Generally speaking called local):
+ // those routes do not require any configuration because the router is already able to forward on
+ // directly attached subnet. In this case, when the driver receive the request to remove,
+ // it will report as removed.
+
+ if (staticRoute.isLocalRoute()) {
+ //if the FlowRule is in PENDING_REMOVE or REMOVED, it is not reported.
+ if (flowEntry.state() == PENDING_REMOVE || flowEntry.state() == REMOVED) {
+ devicesStaticRoutes.remove(staticRoute);
+ } else {
+ //FlowRule is reported installed
+ installedRules.add(flowEntry);
+ devicesStaticRoutes.remove(staticRoute);
+ }
+
+ } else {
+ //if the route is found in the device, the FlowRule is reported installed.
+ if (devicesStaticRoutes.contains(staticRoute)) {
+ installedRules.add(flowEntry);
+ devicesStaticRoutes.remove(staticRoute);
+ }
+ }
+ });
+ });
+
+ if (!devicesStaticRoutes.isEmpty()) {
+ log.info("Found static routes on device {} not installed by ONOS: {}",
+ devId, devicesStaticRoutes);
+// FIXME: enable configuration to purge already installed flows.
+// It cannot be allowed by default because it may remove needed management routes
+// log.warn("Removing from device {} the FlowEntries not expected {}", deviceId, devicesStaticRoutes);
+// devicesStaticRoutes.forEach(staticRoute -> editRoute(session, REMOVE, staticRoute));
+ }
+ return installedRules;
+ }
+
+ @Override
+ public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
+ return manageRules(rules, ADD);
+ }
+
+ @Override
+ public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
+ return manageRules(rules, REMOVE);
+ }
+
+ private Collection<FlowRule> manageRules(Collection<FlowRule> rules, OperationType type) {
+
+ DeviceId deviceId = this.data().deviceId();
+
+ log.debug("{} flow entries to NETCONF device {}", type, deviceId);
+ NetconfController controller = checkNotNull(handler()
+ .get(NetconfController.class));
+ NetconfSession session = controller.getDevicesMap().get(deviceId)
+ .getSession();
+ Collection<FlowRule> managedRules = new HashSet<>();
+
+ for (FlowRule flowRule : rules) {
+
+ Optional<IPCriterion> ipCriterion = getIpCriterion(flowRule);
+ if (!ipCriterion.isPresent()) {
+ log.error("Currently not supported: IPv4 destination match must be used");
+ continue;
+ }
+
+ Optional<OutputInstruction> output = getOutput(flowRule);
+ if (!output.isPresent()) {
+ log.error("Currently not supported: the output action is needed");
+ continue;
+ }
+
+ getStaticRoute(deviceId, ipCriterion.get(), output.get(), flowRule.priority()).ifPresent(
+ staticRoute -> {
+ //If the static route is not local, the driver tries to install
+ if (!staticRoute.isLocalRoute()) {
+ //Only if the installation is successful, the driver report the
+ // FlowRule as installed.
+ if (editRoute(session, type, staticRoute)) {
+ managedRules.add(flowRule);
+ }
+ //If static route are local, they are not installed
+ // because are not required. Directly connected routes
+ //are automatically forwarded.
+ } else {
+ managedRules.add(flowRule);
+ }
+ }
+ );
+ }
+ return rules;
+ }
+
+ private boolean editRoute(NetconfSession session, OperationType type,
+ StaticRoute staticRoute) {
+ try {
+ boolean reply = false;
+ if (type == ADD) {
+ reply = session
+ .editConfig(DatastoreId.CANDIDATE, "merge",
+ routeAddBuilder(staticRoute));
+ } else if (type == REMOVE) {
+ reply = session
+ .editConfig(DatastoreId.CANDIDATE, "none", routeDeleteBuilder(staticRoute));
+ }
+ if (reply && commit()) {
+ return true;
+ } else {
+ if (!rollback()) {
+ log.error("Something went wrong in the configuration and impossible to rollback");
+ } else {
+ log.error("Something went wrong in the configuration: a static route has not been {} {}",
+ type == ADD ? "added" : "removed", staticRoute);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(new NetconfException("Failed to retrieve configuration.",
+ e));
+ }
+ return false;
+ }
+
+ /**
+ * Helper method to convert FlowRule into an abstraction of static route
+ * {@link StaticRoute}.
+ *
+ * @param devId the device id
+ * @param criteria the IP destination criteria
+ * @param output the output instruction
+ * @return optional of Static Route
+ */
+ private Optional<StaticRoute> getStaticRoute(DeviceId devId,
+ IPCriterion criteria,
+ OutputInstruction output,
+ int priority) {
+
+ DeviceService deviceService = this.handler().get(DeviceService.class);
+ Collection<Port> ports = deviceService.getPorts(devId);
+ Optional<Port> port = ports.stream().filter(x -> x.number().equals(output.port())).findAny();
+ if (!port.isPresent()) {
+ log.error("The port {} does not exist in the device",
+ output.port());
+ return Optional.empty();
+ }
+
+ //Find if the route refers to a local interface.
+ Optional<Port> local = deviceService.getPorts(devId).stream().filter(this::isIp)
+ .filter(p -> criteria.ip().getIp4Prefix().contains(
+ Ip4Address.valueOf(p.annotations().value(IP_STRING)))).findAny();
+
+ if (local.isPresent()) {
+ return Optional.of(new StaticRoute(criteria.ip().getIp4Prefix(),
+ criteria.ip().getIp4Prefix().address(), true, priority));
+ }
+
+ Optional<Ip4Address> nextHop = findIpDst(devId, port.get());
+ if (nextHop.isPresent()) {
+ return Optional.of(
+ new StaticRoute(criteria.ip().getIp4Prefix(), nextHop.get(), false, priority));
+ } else {
+ log.error("The destination interface has not an IP {}", port.get());
+ return Optional.empty();
+ }
+
+ }
+
+ /**
+ * Helper method to get the IP destination criterion given a flow rule.
+ *
+ * @param flowRule the flow rule
+ * @return optional of IP destination criterion
+ */
+ private Optional<IPCriterion> getIpCriterion(FlowRule flowRule) {
+
+ Criterion ip = flowRule.selector().getCriterion(Criterion.Type.IPV4_DST);
+ return Optional.ofNullable((IPCriterion) ip);
+ }
+
+ /**
+ * Helper method to get the output instruction given a flow rule.
+ *
+ * @param flowRule the flow rule
+ * @return the output instruction
+ */
+ private Optional<OutputInstruction> getOutput(FlowRule flowRule) {
+ Optional<OutputInstruction> output = flowRule
+ .treatment().allInstructions().stream()
+ .filter(instruction -> instruction
+ .type() == Instruction.Type.OUTPUT)
+ .map(x -> (OutputInstruction) x).findFirst();
+ return output;
+ }
+
+ private String routingTableBuilder() {
+ StringBuilder rpc = new StringBuilder("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
+ rpc.append("<get-route-information/>");
+ rpc.append("</rpc>");
+ return rpc.toString();
+ }
+
+ private boolean commit() {
+ NetconfController controller = checkNotNull(handler()
+ .get(NetconfController.class));
+ NetconfSession session = controller.getDevicesMap()
+ .get(handler().data().deviceId()).getSession();
+
+ String replay;
+ try {
+ replay = session.get(commitBuilder());
+ } catch (IOException e) {
+ throw new RuntimeException(new NetconfException("Failed to retrieve configuration.",
+ e));
+ }
+
+ if (replay != null && replay.indexOf(OK) >= 0) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean rollback() {
+ NetconfController controller = checkNotNull(handler()
+ .get(NetconfController.class));
+ NetconfSession session = controller.getDevicesMap()
+ .get(handler().data().deviceId()).getSession();
+
+ String replay;
+ try {
+ replay = session.get(rollbackBuilder(0));
+ } catch (IOException e) {
+ throw new RuntimeException(new NetconfException("Failed to retrieve configuration.",
+ e));
+ }
+
+ if (replay != null && replay.indexOf(OK) >= 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Helper method to find the next hop IP address.
+ * The logic is to check if the destination ports have an IP address
+ * by checking the logical interface (e.g., for port physical ge-2/0/1,
+ * a logical interface may be ge-2/0/1.0
+ *
+ * @param deviceId the device id of the flow rule to be installed
+ * @param port output port of the flow rule treatment
+ * @return optional IPv4 address of a next hop
+ */
+ private Optional<Ip4Address> findIpDst(DeviceId deviceId, Port port) {
+ LinkService linkService = this.handler().get(LinkService.class);
+ Set<Link> links = linkService.getEgressLinks(new ConnectPoint(deviceId, port.number()));
+ DeviceService deviceService = this.handler().get(DeviceService.class);
+ //Using only links with adjacency discovered by the LLDP protocol (see LinkDiscoveryJuniperImpl)
+ Map<DeviceId, Port> dstPorts = links.stream().filter(l ->
+ IP_STRING.toUpperCase().equals(l.annotations().value("layer")))
+ .collect(Collectors.toMap(
+ l -> l.dst().deviceId(),
+ l -> deviceService.getPort(l.dst().deviceId(), l.dst().port())));
+ for (Map.Entry<DeviceId, Port> entry : dstPorts.entrySet()) {
+ String portName = entry.getValue().annotations().value(AnnotationKeys.PORT_NAME);
+
+ Optional<Port> childPort = deviceService.getPorts(entry.getKey()).stream()
+ .filter(p -> Strings.nullToEmpty(
+ p.annotations().value(AnnotationKeys.PORT_NAME)).contains(portName.trim()))
+ .filter(p -> isIp(p))
+ .findAny();
+ if (childPort.isPresent()) {
+ return Optional.ofNullable(Ip4Address.valueOf(childPort.get().annotations().value("ip")));
+ }
+ }
+
+ return Optional.empty();
+ }
+
+ /**
+ * Helper method to find if an interface has an IP address.
+ * It will check the annotations of the port.
+ *
+ * @param port the port
+ * @return true if the IP address is present. Otherwise false.
+ */
+ private boolean isIp(Port port) {
+ String ip4 = port.annotations().value(IP_STRING);
+ if (StringUtils.isEmpty(ip4)) {
+ return false;
+ }
+ try {
+
+ Ip4Address.valueOf(port.annotations().value(IP_STRING));
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/drivers/juniper/src/main/java/org/onosproject/drivers/juniper/JuniperUtils.java b/drivers/juniper/src/main/java/org/onosproject/drivers/juniper/JuniperUtils.java
index 3dc94df..a67c2f6 100644
--- a/drivers/juniper/src/main/java/org/onosproject/drivers/juniper/JuniperUtils.java
+++ b/drivers/juniper/src/main/java/org/onosproject/drivers/juniper/JuniperUtils.java
@@ -17,7 +17,10 @@
package org.onosproject.drivers.juniper;
import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.lang.StringUtils;
import org.onlab.packet.ChassisId;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
import org.onlab.packet.MacAddress;
import org.onosproject.net.AnnotationKeys;
import org.onosproject.net.ConnectPoint;
@@ -39,18 +42,23 @@
import com.google.common.base.Strings;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import static org.onosproject.drivers.juniper.StaticRoute.DEFAULT_METRIC_STATIC_ROUTE;
+import static org.onosproject.drivers.juniper.StaticRoute.toFlowRulePriority;
import static org.onosproject.net.Device.Type.ROUTER;
import static org.onosproject.net.PortNumber.portNumber;
import static org.slf4j.LoggerFactory.getLogger;
// Ref: Junos YANG:
// https://github.com/Juniper/yang
+
/**
* Utility class for Netconf XML for Juniper.
* Tested with MX240 junos 14.2
@@ -96,6 +104,8 @@
private static final Pattern ADD_PATTERN =
Pattern.compile(REGEX_ADD, Pattern.DOTALL);
+ public static final String PROTOCOL_NAME = "protocol-name";
+
private static final String JUNIPER = "JUNIPER";
private static final String UNKNOWN = "UNKNOWN";
@@ -161,6 +171,80 @@
}
/**
+ * Helper method to commit a config.
+ *
+ * @return string contains the result of the commit
+ */
+ public static String commitBuilder() {
+ return RPC_TAG_NETCONF_BASE +
+ "<commit/>" + RPC_CLOSE_TAG;
+ }
+
+ /**
+ * Helper method to build the schema for returning to a previously
+ * committed configuration.
+ *
+ * @param versionToReturn Configuration to return to. The range of values is from 0 through 49.
+ * The most recently saved configuration is number 0,
+ * and the oldest saved configuration is number 49.
+ * @return string containing the XML schema
+ */
+ public static String rollbackBuilder(int versionToReturn) {
+ return RPC_TAG_NETCONF_BASE +
+ "<get-rollback-information>" +
+ "<rollback>" + versionToReturn + "</rollback>" +
+ "</get-rollback-information>" +
+ RPC_CLOSE_TAG;
+ }
+
+
+ /**
+ * Helper method to build an XML schema to configure a static route
+ * given a {@link StaticRoute}.
+ *
+ * @param staticRoute the static route to be configured
+ * @return string contains the result of the configuration
+ */
+ public static String routeAddBuilder(StaticRoute staticRoute) {
+ StringBuilder rpc = new StringBuilder("<configuration>\n");
+ rpc.append("<routing-options>\n");
+ rpc.append("<static>\n");
+ rpc.append("<route>\n");
+ rpc.append("<destination>" + staticRoute.ipv4Dst().toString() + "</destination>\n");
+ rpc.append("<next-hop>" + staticRoute.nextHop() + "</next-hop>\n");
+
+ if (staticRoute.getMetric() != DEFAULT_METRIC_STATIC_ROUTE) {
+ rpc.append("<metric>" + staticRoute.getMetric() + "</metric>");
+ }
+
+ rpc.append("</route>\n");
+ rpc.append("</static>\n");
+ rpc.append("</routing-options>\n");
+ rpc.append("</configuration>\n");
+
+ return rpc.toString();
+ }
+
+ /**
+ * Helper method to build a XML schema to delete a static route
+ * given a {@link StaticRoute}.
+ * @param staticRoute the static route to be deleted
+ * @return string contains the result of the configuratio
+ */
+ public static String routeDeleteBuilder(StaticRoute staticRoute) {
+ return "<configuration>\n" +
+ "<routing-options>\n" +
+ "<static>\n" +
+ "<route operation=\"delete\">\n" +
+ "<name>" + staticRoute.ipv4Dst().toString() + "</name>\n" +
+ "</route>\n" +
+ "</static>\n" +
+ "</routing-options>\n" +
+ "</configuration>\n";
+ }
+
+
+ /**
* Parses device configuration and returns the device description.
*
* @param deviceId the id of the device
@@ -458,4 +542,68 @@
this.remotePortIndex = pIndex;
}
}
+
+ protected enum OperationType {
+ ADD,
+ REMOVE,
+ }
+
+ /**
+ * Parses {@literal route-information} tree.
+ * This implementation supports only static routes.
+ *
+ * @param cfg route-information
+ * @return a collection of static routes
+ */
+ public static Collection<StaticRoute> parseRoutingTable(HierarchicalConfiguration cfg) {
+
+ Collection<StaticRoute> staticRoutes = new HashSet<>();
+ HierarchicalConfiguration routeInfo =
+ cfg.configurationAt("route-information");
+ List<HierarchicalConfiguration> routeTables = routeInfo.configurationsAt("route-table");
+ for (HierarchicalConfiguration routeTable : routeTables) {
+ List<HierarchicalConfiguration> routes = routeTable.configurationsAt("rt");
+ for (HierarchicalConfiguration route : routes) {
+ if (route != null) {
+ HierarchicalConfiguration rtEntry = route.configurationAt("rt-entry");
+ if (rtEntry.getString(PROTOCOL_NAME) != null &&
+ rtEntry.getString(PROTOCOL_NAME).contains("Static")) {
+ parseStaticRoute(rtEntry,
+ route.getString("rt-destination"),
+ rtEntry.getString("metric"))
+ .ifPresent(x -> staticRoutes.add(x));
+
+ }
+ }
+ }
+ }
+ return staticRoutes;
+ }
+
+ /**
+ * Parse the {@literal rt-entry} for static routes.
+ *
+ * @param rtEntry rt-entry filtered by {@literal protocol-name} equals to Static
+ * @param destination rt-destination
+ * @return optional of static route
+ */
+ private static Optional<StaticRoute> parseStaticRoute(HierarchicalConfiguration rtEntry,
+ String destination, String metric) {
+
+ Ip4Prefix ipDst = Ip4Prefix.valueOf(destination);
+
+ HierarchicalConfiguration nextHop = rtEntry.configurationAt("nh");
+ String to = nextHop.getString("to");
+ if (StringUtils.isEmpty(to)) {
+ return Optional.empty();
+ }
+ Ip4Address nextHopIp = Ip4Address.valueOf(to);
+
+ if (metric == null) {
+ return Optional.of(new StaticRoute(ipDst, nextHopIp, false));
+ } else {
+ return Optional.of(new StaticRoute(ipDst, nextHopIp, false,
+ toFlowRulePriority(Integer.parseInt(metric))));
+ }
+ }
}
diff --git a/drivers/juniper/src/main/java/org/onosproject/drivers/juniper/StaticRoute.java b/drivers/juniper/src/main/java/org/onosproject/drivers/juniper/StaticRoute.java
new file mode 100644
index 0000000..f83d0bb
--- /dev/null
+++ b/drivers/juniper/src/main/java/org/onosproject/drivers/juniper/StaticRoute.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * 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 org.onosproject.drivers.juniper;
+
+import com.google.common.annotations.Beta;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static org.onosproject.net.intent.Intent.DEFAULT_INTENT_PRIORITY;
+
+
+/**
+ * Definition of a static route for the Juniper.
+ */
+@Beta
+public class StaticRoute {
+
+ /**
+ * Maximum metric value for the Juniper.
+ */
+ private static final int BEST_STATIC_METRIC = 1;
+
+ /**
+ * Max FlowRule priority that corresponds to the BEST_STATIC_METRIC of the Juniper.
+ * DEFAULT_INTENT_PRIORITY + DEFAULT_METRIC_STATIC_ROUTE - BEST_STATIC_METRIC
+ */
+ private static final int MAX_FLOWRULE_PRIORITY_ACCEPTED = 104;
+
+ /**
+ * Default static route metric.
+ */
+ public static final int DEFAULT_METRIC_STATIC_ROUTE = 5;
+
+ private final Ip4Prefix ipv4Dest;
+ private final Ip4Address nextHop;
+ private final int priority;
+ private final boolean localRoute;
+
+ public StaticRoute(Ip4Prefix ipv4Dest, Ip4Address nextHop, boolean localRoute, int priority) {
+ this.ipv4Dest = ipv4Dest;
+ this.nextHop = nextHop;
+ this.priority = priority;
+ this.localRoute = localRoute;
+ }
+
+ public StaticRoute(Ip4Prefix ipv4Dest, Ip4Address nextHop, boolean localRoute) {
+ this(ipv4Dest, nextHop, localRoute, DEFAULT_INTENT_PRIORITY);
+ }
+
+ public Ip4Prefix ipv4Dst() {
+ return ipv4Dest;
+ }
+
+ public Ip4Address nextHop() {
+ return nextHop;
+ }
+
+ /**
+ * Method to covert the priority into the Juniper metric.
+ * See https://goo.gl/ACo952.
+ * @return the metric for the Juniper
+ */
+ public int getMetric() {
+ if (priority > MAX_FLOWRULE_PRIORITY_ACCEPTED) {
+ return BEST_STATIC_METRIC;
+ }
+ return DEFAULT_INTENT_PRIORITY + DEFAULT_METRIC_STATIC_ROUTE - priority;
+ }
+
+ /**
+ * Method to convert the Juniper metric into a priority.
+ *
+ * @param metric the metric to be converted
+ * @return the priority
+ */
+ public static int toFlowRulePriority(int metric) {
+ return DEFAULT_INTENT_PRIORITY + DEFAULT_METRIC_STATIC_ROUTE - metric;
+ }
+
+ /**
+ * Directly attached routes are called local route. Such local routes are directly discovered by
+ * the router itself.
+ *
+ * @return true if it is a local route
+ */
+ public boolean isLocalRoute() {
+ return localRoute;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof StaticRoute) {
+ StaticRoute that = (StaticRoute) obj;
+ return Objects.equals(ipv4Dest, that.ipv4Dest) &&
+ Objects.equals(nextHop, that.nextHop) &&
+ Objects.equals(priority, that.priority) &&
+ Objects.equals(localRoute, that.localRoute);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ipv4Dest, nextHop, priority, localRoute);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .omitNullValues()
+ .addValue(localRoute ? "Local route" : null)
+ .add("IP address destination", ipv4Dest)
+ .add("Next Hop IP address", nextHop)
+ .add("Priority", priority)
+ .toString();
+ }
+}
diff --git a/drivers/juniper/src/main/resources/juniper-drivers.xml b/drivers/juniper/src/main/resources/juniper-drivers.xml
index a4b1150..f5d3e0e 100644
--- a/drivers/juniper/src/main/resources/juniper-drivers.xml
+++ b/drivers/juniper/src/main/resources/juniper-drivers.xml
@@ -19,6 +19,8 @@
hwVersion="" swVersion="JunOS">
<behaviour api="org.onosproject.net.device.DeviceDescriptionDiscovery"
impl="org.onosproject.drivers.juniper.DeviceDiscoveryJuniperImpl"/>
+ <behaviour api="org.onosproject.net.flow.FlowRuleProgrammable"
+ impl="org.onosproject.drivers.juniper.FlowRuleJuniperImpl"/>
<behaviour api="org.onosproject.net.behaviour.LinkDiscovery"
impl="org.onosproject.drivers.juniper.LinkDiscoveryJuniperImpl"/>
</driver>