From FlowRule to static routes for Juniper

Conversion of FlowRules into static routes and retrieve of
installed static routes as FlowRules.

Change-Id: If960e4d15e431ae8f5ea75eff83913056a51c852
This commit is contained in:
Michele Santuari 2017-06-23 14:44:01 +02:00 committed by Yuta HIGUCHI
parent 98dfdb756a
commit ce25649ff6
4 changed files with 712 additions and 0 deletions

View File

@ -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;
}
}

View File

@ -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 org.slf4j.Logger;
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 @@ public final class JuniperUtils {
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";
@ -160,6 +170,80 @@ public final class JuniperUtils {
request + RPC_CLOSE_TAG;
}
/**
* 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.
*
@ -458,4 +542,68 @@ public final class JuniperUtils {
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))));
}
}
}

View File

@ -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();
}
}

View File

@ -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>