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>