mirror of
https://github.com/opennetworkinglab/onos.git
synced 2026-05-05 04:06:49 +02:00
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:
parent
98dfdb756a
commit
ce25649ff6
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user