diff options
Diffstat (limited to 'framework/src/onos/apps/segmentrouting')
50 files changed, 8613 insertions, 0 deletions
diff --git a/framework/src/onos/apps/segmentrouting/pom.xml b/framework/src/onos/apps/segmentrouting/pom.xml new file mode 100644 index 00000000..3ed4c4fe --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/pom.xml @@ -0,0 +1,137 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2014 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>onos-apps</artifactId> + <groupId>org.onosproject</groupId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-app-segmentrouting</artifactId> + <packaging>bundle</packaging> + + <description>Segment routing application</description> + + <properties> + <onos.app.name>org.onosproject.segmentrouting</onos.app.name> + <web.context>/onos/segmentrouting</web.context> + <api.version>1.0.0</api.version> + <api.title>ONOS Segment Routing REST API</api.title> + <api.description> + APIs for interacting with the Segment Routing application. + </api.description> + <api.package>org.onosproject.segmentrouting.web</api.package> + </properties> + + <dependencies> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-cli</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.karaf.shell</groupId> + <artifactId>org.apache.karaf.shell.console</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-rest</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onlab-rest</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>javax.ws.rs</groupId> + <artifactId>jsr311-api</artifactId> + <version>1.1.1</version> + </dependency> + <dependency> + <groupId>com.sun.jersey</groupId> + <artifactId>jersey-servlet</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> + + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-annotations</artifactId> + </dependency> + + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <_wab>src/main/webapp/</_wab> + <Include-Resource> + WEB-INF/classes/apidoc/swagger.json=target/swagger.json, + {maven-resources} + </Include-Resource> + <Bundle-SymbolicName> + ${project.groupId}.${project.artifactId} + </Bundle-SymbolicName> + <Import-Package> + org.slf4j, + org.osgi.framework, + javax.ws.rs, + javax.ws.rs.core, + com.sun.jersey.api.core, + com.sun.jersey.spi.container.servlet, + com.sun.jersey.server.impl.container.servlet, + com.fasterxml.jackson.databind, + com.fasterxml.jackson.databind.node, + com.fasterxml.jackson.core, + org.apache.karaf.shell.commands, + org.apache.commons.lang.math.*, + com.google.common.*, + org.onlab.packet.*, + org.onlab.rest.*, + org.onosproject.*, + org.onlab.util.*, + org.jboss.netty.util.* + </Import-Package> + <Web-ContextPath>${web.context}</Web-ContextPath> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java new file mode 100644 index 00000000..6ca6d193 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ArpHandler.java @@ -0,0 +1,216 @@ +/* + * Copyright 2015 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.segmentrouting; + +import org.onlab.packet.ARP; +import org.onlab.packet.Ethernet; +import org.onlab.packet.Ip4Address; +import org.onlab.packet.IpAddress; +import org.onlab.packet.MacAddress; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Host; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.packet.DefaultOutboundPacket; +import org.onosproject.net.packet.InboundPacket; +import org.onosproject.net.HostId; +import org.onosproject.net.packet.OutboundPacket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.util.List; +import static com.google.common.base.Preconditions.checkNotNull; + +public class ArpHandler { + + private static Logger log = LoggerFactory.getLogger(ArpHandler.class); + + private SegmentRoutingManager srManager; + private DeviceConfiguration config; + + /** + * Creates an ArpHandler object. + * + * @param srManager SegmentRoutingManager object + */ + public ArpHandler(SegmentRoutingManager srManager) { + this.srManager = srManager; + this.config = checkNotNull(srManager.deviceConfiguration); + } + + /** + * Processes incoming ARP packets. + * If it is an ARP request to router itself or known hosts, + * then it sends ARP response. + * If it is an ARP request to unknown hosts in its own subnet, + * then it flood the ARP request to the ports. + * If it is an ARP response, then set a flow rule for the host + * and forward any IP packets to the host in the packet buffer to the host. + * + * @param pkt incoming packet + */ + public void processPacketIn(InboundPacket pkt) { + + Ethernet ethernet = pkt.parsed(); + ARP arp = (ARP) ethernet.getPayload(); + + ConnectPoint connectPoint = pkt.receivedFrom(); + PortNumber inPort = connectPoint.port(); + DeviceId deviceId = connectPoint.deviceId(); + byte[] senderMacAddressByte = arp.getSenderHardwareAddress(); + Ip4Address hostIpAddress = Ip4Address.valueOf(arp.getSenderProtocolAddress()); + + srManager.routingRulePopulator.populateIpRuleForHost(deviceId, hostIpAddress, MacAddress. + valueOf(senderMacAddressByte), inPort); + + if (arp.getOpCode() == ARP.OP_REQUEST) { + handleArpRequest(deviceId, connectPoint, ethernet); + } else { + srManager.ipHandler.forwardPackets(deviceId, hostIpAddress); + } + } + + private void handleArpRequest(DeviceId deviceId, ConnectPoint inPort, Ethernet payload) { + + ARP arpRequest = (ARP) payload.getPayload(); + HostId targetHostId = HostId.hostId(MacAddress.valueOf( + arpRequest.getTargetHardwareAddress())); + + // ARP request for router + if (isArpReqForRouter(deviceId, arpRequest)) { + Ip4Address targetAddress = Ip4Address.valueOf(arpRequest.getTargetProtocolAddress()); + + sendArpResponse(arpRequest, config.getRouterMacForAGatewayIp(targetAddress)); + // ARP request for known hosts + } else if (srManager.hostService.getHost(targetHostId) != null) { + MacAddress targetMac = srManager.hostService.getHost(targetHostId).mac(); + sendArpResponse(arpRequest, targetMac); + + // ARP request for unknown host in the subnet + } else if (isArpReqForSubnet(deviceId, arpRequest)) { + flood(payload, inPort); + } + } + + + private boolean isArpReqForRouter(DeviceId deviceId, ARP arpRequest) { + List<Ip4Address> gatewayIpAddresses = config.getSubnetGatewayIps(deviceId); + if (gatewayIpAddresses != null) { + Ip4Address targetProtocolAddress = Ip4Address.valueOf(arpRequest + .getTargetProtocolAddress()); + if (gatewayIpAddresses.contains(targetProtocolAddress)) { + return true; + } + } + return false; + } + + private boolean isArpReqForSubnet(DeviceId deviceId, ARP arpRequest) { + return config.getSubnets(deviceId).stream() + .anyMatch((prefix)-> + prefix.contains(Ip4Address. + valueOf(arpRequest. + getTargetProtocolAddress()))); + } + + /** + * Sends an APR request for the target IP address to all ports except in-port. + * + * @param deviceId Switch device ID + * @param targetAddress target IP address for ARP + * @param inPort in-port + */ + public void sendArpRequest(DeviceId deviceId, IpAddress targetAddress, ConnectPoint inPort) { + + byte[] senderMacAddress = config.getDeviceMac(deviceId).toBytes(); + byte[] senderIpAddress = config.getRouterIp(deviceId).toOctets(); + + ARP arpRequest = new ARP(); + arpRequest.setHardwareType(ARP.HW_TYPE_ETHERNET) + .setProtocolType(ARP.PROTO_TYPE_IP) + .setHardwareAddressLength( + (byte) Ethernet.DATALAYER_ADDRESS_LENGTH) + .setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH) + .setOpCode(ARP.OP_REQUEST) + .setSenderHardwareAddress(senderMacAddress) + .setTargetHardwareAddress(MacAddress.ZERO.toBytes()) + .setSenderProtocolAddress(senderIpAddress) + .setTargetProtocolAddress(targetAddress.toOctets()); + + Ethernet eth = new Ethernet(); + eth.setDestinationMACAddress(MacAddress.BROADCAST.toBytes()) + .setSourceMACAddress(senderMacAddress) + .setEtherType(Ethernet.TYPE_ARP).setPayload(arpRequest); + + flood(eth, inPort); + } + + private void sendArpResponse(ARP arpRequest, MacAddress targetMac) { + + ARP arpReply = new ARP(); + arpReply.setHardwareType(ARP.HW_TYPE_ETHERNET) + .setProtocolType(ARP.PROTO_TYPE_IP) + .setHardwareAddressLength( + (byte) Ethernet.DATALAYER_ADDRESS_LENGTH) + .setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH) + .setOpCode(ARP.OP_REPLY) + .setSenderHardwareAddress(targetMac.toBytes()) + .setSenderProtocolAddress(arpRequest.getTargetProtocolAddress()) + .setTargetHardwareAddress(arpRequest.getSenderHardwareAddress()) + .setTargetProtocolAddress(arpRequest.getSenderProtocolAddress()); + + Ethernet eth = new Ethernet(); + eth.setDestinationMACAddress(arpRequest.getSenderHardwareAddress()) + .setSourceMACAddress(targetMac.toBytes()) + .setEtherType(Ethernet.TYPE_ARP).setPayload(arpReply); + + + HostId dstId = HostId.hostId(MacAddress.valueOf( + arpReply.getTargetHardwareAddress())); + Host dst = srManager.hostService.getHost(dstId); + if (dst == null) { + log.warn("Cannot send ARP response to unknown device"); + return; + } + + TrafficTreatment treatment = DefaultTrafficTreatment.builder(). + setOutput(dst.location().port()).build(); + OutboundPacket packet = new DefaultOutboundPacket(dst.location().deviceId(), + treatment, ByteBuffer.wrap(eth.serialize())); + + srManager.packetService.emit(packet); + } + + private void flood(Ethernet request, ConnectPoint inPort) { + TrafficTreatment.Builder builder; + ByteBuffer buf = ByteBuffer.wrap(request.serialize()); + + for (Port port: srManager.deviceService.getPorts(inPort.deviceId())) { + if (!port.number().equals(inPort.port()) && + port.number().toLong() > 0) { + builder = DefaultTrafficTreatment.builder(); + builder.setOutput(port.number()); + srManager.packetService.emit(new DefaultOutboundPacket(inPort.deviceId(), + builder.build(), buf)); + } + } + } + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java new file mode 100644 index 00000000..8b447af4 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DefaultRoutingHandler.java @@ -0,0 +1,551 @@ +/* + * Copyright 2015 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.segmentrouting; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.onlab.packet.Ip4Address; +import org.onlab.packet.Ip4Prefix; +import org.onlab.packet.IpPrefix; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.MastershipRole; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class DefaultRoutingHandler { + + private static Logger log = LoggerFactory + .getLogger(DefaultRoutingHandler.class); + + private SegmentRoutingManager srManager; + private RoutingRulePopulator rulePopulator; + private HashMap<DeviceId, ECMPShortestPathGraph> currentEcmpSpgMap; + private HashMap<DeviceId, ECMPShortestPathGraph> updatedEcmpSpgMap; + private DeviceConfiguration config; + private final Lock statusLock = new ReentrantLock(); + private volatile Status populationStatus; + + /** + * Represents the default routing population status. + */ + public enum Status { + // population process is not started yet. + IDLE, + + // population process started. + STARTED, + + // population process was aborted due to errors, mostly for groups not + // found. + ABORTED, + + // population process was finished successfully. + SUCCEEDED + } + + /** + * Creates a DefaultRoutingHandler object. + * + * @param srManager SegmentRoutingManager object + */ + public DefaultRoutingHandler(SegmentRoutingManager srManager) { + this.srManager = srManager; + this.rulePopulator = checkNotNull(srManager.routingRulePopulator); + this.config = checkNotNull(srManager.deviceConfiguration); + this.populationStatus = Status.IDLE; + this.currentEcmpSpgMap = Maps.newHashMap(); + } + + /** + * Populates all routing rules to all connected routers, including default + * routing rules, adjacency rules, and policy rules if any. + * + * @return true if it succeeds in populating all rules, otherwise false + */ + public boolean populateAllRoutingRules() { + + statusLock.lock(); + try { + populationStatus = Status.STARTED; + rulePopulator.resetCounter(); + log.info("Starts to populate routing rules"); + log.debug("populateAllRoutingRules: populationStatus is STARTED"); + + for (Device sw : srManager.deviceService.getDevices()) { + if (srManager.mastershipService.getLocalRole(sw.id()) != MastershipRole.MASTER) { + log.debug("populateAllRoutingRules: skipping device {}...we are not master", + sw.id()); + continue; + } + + ECMPShortestPathGraph ecmpSpg = new ECMPShortestPathGraph(sw.id(), srManager); + if (!populateEcmpRoutingRules(sw.id(), ecmpSpg)) { + log.debug("populateAllRoutingRules: populationStatus is ABORTED"); + populationStatus = Status.ABORTED; + log.debug("Abort routing rule population"); + return false; + } + currentEcmpSpgMap.put(sw.id(), ecmpSpg); + + // TODO: Set adjacency routing rule for all switches + } + + log.debug("populateAllRoutingRules: populationStatus is SUCCEEDED"); + populationStatus = Status.SUCCEEDED; + log.info("Completes routing rule population. Total # of rules pushed : {}", + rulePopulator.getCounter()); + return true; + } finally { + statusLock.unlock(); + } + } + + /** + * Populates the routing rules according to the route changes due to the link + * failure or link add. It computes the routes changed due to the link changes and + * repopulates the rules only for the routes. + * + * @param linkFail link failed, null for link added + * @return true if it succeeds to populate all rules, false otherwise + */ + public boolean populateRoutingRulesForLinkStatusChange(Link linkFail) { + + statusLock.lock(); + try { + + if (populationStatus == Status.STARTED) { + log.warn("Previous rule population is not finished."); + return true; + } + + // Take the snapshots of the links + updatedEcmpSpgMap = new HashMap<>(); + for (Device sw : srManager.deviceService.getDevices()) { + if (srManager.mastershipService. + getLocalRole(sw.id()) != MastershipRole.MASTER) { + continue; + } + ECMPShortestPathGraph ecmpSpgUpdated = + new ECMPShortestPathGraph(sw.id(), srManager); + updatedEcmpSpgMap.put(sw.id(), ecmpSpgUpdated); + } + + log.info("Starts rule population from link change"); + + Set<ArrayList<DeviceId>> routeChanges; + log.trace("populateRoutingRulesForLinkStatusChange: " + + "populationStatus is STARTED"); + populationStatus = Status.STARTED; + if (linkFail == null) { + // Compare all routes of existing ECMP SPG with the new ones + routeChanges = computeRouteChange(); + } else { + // Compare existing ECMP SPG only with the link removed + routeChanges = computeDamagedRoutes(linkFail); + } + + if (routeChanges.isEmpty()) { + log.info("No route changes for the link status change"); + log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED"); + populationStatus = Status.SUCCEEDED; + return true; + } + + if (repopulateRoutingRulesForRoutes(routeChanges)) { + log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is SUCCEEDED"); + populationStatus = Status.SUCCEEDED; + log.info("Complete to repopulate the rules. # of rules populated : {}", + rulePopulator.getCounter()); + return true; + } else { + log.debug("populateRoutingRulesForLinkStatusChange: populationStatus is ABORTED"); + populationStatus = Status.ABORTED; + log.warn("Failed to repopulate the rules."); + return false; + } + } finally { + statusLock.unlock(); + } + } + + private boolean repopulateRoutingRulesForRoutes(Set<ArrayList<DeviceId>> routes) { + rulePopulator.resetCounter(); + HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> routesBydevice = + new HashMap<>(); + for (ArrayList<DeviceId> link: routes) { + // When only the source device is defined, reinstall routes to all other devices + if (link.size() == 1) { + log.trace("repopulateRoutingRulesForRoutes: running ECMP graph for device {}", link.get(0)); + ECMPShortestPathGraph ecmpSpg = new ECMPShortestPathGraph(link.get(0), srManager); + if (populateEcmpRoutingRules(link.get(0), ecmpSpg)) { + log.debug("Populating flow rules from {} to all is successful", + link.get(0)); + currentEcmpSpgMap.put(link.get(0), ecmpSpg); + } else { + log.warn("Failed to populate the flow rules from {} to all", link.get(0)); + return false; + } + } else { + ArrayList<ArrayList<DeviceId>> deviceRoutes = + routesBydevice.get(link.get(1)); + if (deviceRoutes == null) { + deviceRoutes = new ArrayList<>(); + routesBydevice.put(link.get(1), deviceRoutes); + } + deviceRoutes.add(link); + } + } + + for (DeviceId impactedDevice : routesBydevice.keySet()) { + ArrayList<ArrayList<DeviceId>> deviceRoutes = + routesBydevice.get(impactedDevice); + for (ArrayList<DeviceId> link: deviceRoutes) { + log.debug("repopulate RoutingRules For Routes {} -> {}", + link.get(0), link.get(1)); + DeviceId src = link.get(0); + DeviceId dst = link.get(1); + ECMPShortestPathGraph ecmpSpg = updatedEcmpSpgMap.get(dst); + HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia = + ecmpSpg.getAllLearnedSwitchesAndVia(); + for (Integer itrIdx : switchVia.keySet()) { + HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap = + switchVia.get(itrIdx); + for (DeviceId targetSw : swViaMap.keySet()) { + if (!targetSw.equals(src)) { + continue; + } + Set<DeviceId> nextHops = new HashSet<>(); + for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) { + if (via.isEmpty()) { + nextHops.add(dst); + } else { + nextHops.add(via.get(0)); + } + } + if (!populateEcmpRoutingRulePartial(targetSw, dst, nextHops)) { + return false; + } + log.debug("Populating flow rules from {} to {} is successful", + targetSw, dst); + } + } + //currentEcmpSpgMap.put(dst, ecmpSpg); + } + //Only if all the flows for all impacted routes to a + //specific target are pushed successfully, update the + //ECMP graph for that target. (Or else the next event + //would not see any changes in the ECMP graphs) + currentEcmpSpgMap.put(impactedDevice, + updatedEcmpSpgMap.get(impactedDevice)); + } + return true; + } + + private Set<ArrayList<DeviceId>> computeDamagedRoutes(Link linkFail) { + + Set<ArrayList<DeviceId>> routes = new HashSet<>(); + + for (Device sw : srManager.deviceService.getDevices()) { + log.debug("Computing the impacted routes for device {} due to link fail", + sw.id()); + if (srManager.mastershipService. + getLocalRole(sw.id()) != MastershipRole.MASTER) { + continue; + } + ECMPShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(sw.id()); + if (ecmpSpg == null) { + log.error("No existing ECMP graph for switch {}", sw.id()); + continue; + } + HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia = + ecmpSpg.getAllLearnedSwitchesAndVia(); + for (Integer itrIdx : switchVia.keySet()) { + HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap = + switchVia.get(itrIdx); + for (DeviceId targetSw : swViaMap.keySet()) { + DeviceId destSw = sw.id(); + Set<ArrayList<DeviceId>> subLinks = + computeLinks(targetSw, destSw, swViaMap); + for (ArrayList<DeviceId> alink: subLinks) { + if ((alink.get(0).equals(linkFail.src().deviceId()) && + alink.get(1).equals(linkFail.dst().deviceId())) + || + (alink.get(0).equals(linkFail.dst().deviceId()) && + alink.get(1).equals(linkFail.src().deviceId()))) { + log.debug("Impacted route:{}->{}", targetSw, destSw); + ArrayList<DeviceId> aRoute = new ArrayList<>(); + aRoute.add(targetSw); + aRoute.add(destSw); + routes.add(aRoute); + break; + } + } + } + } + + } + + return routes; + } + + private Set<ArrayList<DeviceId>> computeRouteChange() { + + Set<ArrayList<DeviceId>> routes = new HashSet<>(); + + for (Device sw : srManager.deviceService.getDevices()) { + log.debug("Computing the impacted routes for device {}", + sw.id()); + if (srManager.mastershipService. + getLocalRole(sw.id()) != MastershipRole.MASTER) { + log.debug("No mastership for {} and skip route optimization", + sw.id()); + continue; + } + + log.trace("link of {} - ", sw.id()); + for (Link link: srManager.linkService.getDeviceLinks(sw.id())) { + log.trace("{} -> {} ", link.src().deviceId(), link.dst().deviceId()); + } + + log.debug("Checking route change for switch {}", sw.id()); + ECMPShortestPathGraph ecmpSpg = currentEcmpSpgMap.get(sw.id()); + if (ecmpSpg == null) { + log.debug("No existing ECMP graph for device {}", sw.id()); + ArrayList<DeviceId> route = new ArrayList<>(); + route.add(sw.id()); + routes.add(route); + continue; + } + ECMPShortestPathGraph newEcmpSpg = updatedEcmpSpgMap.get(sw.id()); + //currentEcmpSpgMap.put(sw.id(), newEcmpSpg); + HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia = + ecmpSpg.getAllLearnedSwitchesAndVia(); + HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchViaUpdated = + newEcmpSpg.getAllLearnedSwitchesAndVia(); + + for (Integer itrIdx : switchViaUpdated.keySet()) { + HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMapUpdated = + switchViaUpdated.get(itrIdx); + for (DeviceId srcSw : swViaMapUpdated.keySet()) { + ArrayList<ArrayList<DeviceId>> viaUpdated = swViaMapUpdated.get(srcSw); + ArrayList<ArrayList<DeviceId>> via = getVia(switchVia, srcSw); + if ((via == null) || !viaUpdated.equals(via)) { + log.debug("Impacted route:{}->{}", srcSw, sw.id()); + ArrayList<DeviceId> route = new ArrayList<>(); + route.add(srcSw); + route.add(sw.id()); + routes.add(route); + } + } + } + } + + for (ArrayList<DeviceId> link: routes) { + log.trace("Route changes - "); + if (link.size() == 1) { + log.trace(" : {} - all", link.get(0)); + } else { + log.trace(" : {} - {}", link.get(0), link.get(1)); + } + } + + return routes; + } + + private ArrayList<ArrayList<DeviceId>> getVia(HashMap<Integer, HashMap<DeviceId, + ArrayList<ArrayList<DeviceId>>>> switchVia, DeviceId srcSw) { + for (Integer itrIdx : switchVia.keySet()) { + HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap = + switchVia.get(itrIdx); + if (swViaMap.get(srcSw) == null) { + continue; + } else { + return swViaMap.get(srcSw); + } + } + + return null; + } + + private Set<ArrayList<DeviceId>> computeLinks(DeviceId src, + DeviceId dst, + HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> viaMap) { + Set<ArrayList<DeviceId>> subLinks = Sets.newHashSet(); + for (ArrayList<DeviceId> via : viaMap.get(src)) { + DeviceId linkSrc = src; + DeviceId linkDst = dst; + for (DeviceId viaDevice: via) { + ArrayList<DeviceId> link = new ArrayList<>(); + linkDst = viaDevice; + link.add(linkSrc); + link.add(linkDst); + subLinks.add(link); + linkSrc = viaDevice; + } + ArrayList<DeviceId> link = new ArrayList<>(); + link.add(linkSrc); + link.add(dst); + subLinks.add(link); + } + + return subLinks; + } + + private boolean populateEcmpRoutingRules(DeviceId destSw, + ECMPShortestPathGraph ecmpSPG) { + + HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> switchVia = ecmpSPG + .getAllLearnedSwitchesAndVia(); + for (Integer itrIdx : switchVia.keySet()) { + HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swViaMap = switchVia + .get(itrIdx); + for (DeviceId targetSw : swViaMap.keySet()) { + Set<DeviceId> nextHops = new HashSet<>(); + + for (ArrayList<DeviceId> via : swViaMap.get(targetSw)) { + if (via.isEmpty()) { + nextHops.add(destSw); + } else { + nextHops.add(via.get(0)); + } + } + if (!populateEcmpRoutingRulePartial(targetSw, destSw, nextHops)) { + return false; + } + } + } + + return true; + } + + private boolean populateEcmpRoutingRulePartial(DeviceId targetSw, + DeviceId destSw, + Set<DeviceId> nextHops) { + boolean result; + + if (nextHops.isEmpty()) { + nextHops.add(destSw); + } + + // If both target switch and dest switch are edge routers, then set IP + // rule for both subnet and router IP. + if (config.isEdgeDevice(targetSw) && config.isEdgeDevice(destSw)) { + List<Ip4Prefix> subnets = config.getSubnets(destSw); + log.debug("populateEcmpRoutingRulePartial in device {} towards {} for subnets {}", + targetSw, destSw, subnets); + result = rulePopulator.populateIpRuleForSubnet(targetSw, + subnets, + destSw, + nextHops); + if (!result) { + return false; + } + + Ip4Address routerIp = config.getRouterIp(destSw); + IpPrefix routerIpPrefix = IpPrefix.valueOf(routerIp, IpPrefix.MAX_INET_MASK_LENGTH); + log.debug("populateEcmpRoutingRulePartial in device {} towards {} for router IP {}", + targetSw, destSw, routerIpPrefix); + result = rulePopulator.populateIpRuleForRouter(targetSw, routerIpPrefix, destSw, nextHops); + if (!result) { + return false; + } + + // If the target switch is an edge router, then set IP rules for the router IP. + } else if (config.isEdgeDevice(targetSw)) { + Ip4Address routerIp = config.getRouterIp(destSw); + IpPrefix routerIpPrefix = IpPrefix.valueOf(routerIp, IpPrefix.MAX_INET_MASK_LENGTH); + log.debug("populateEcmpRoutingRulePartial in device {} towards {} for router IP {}", + targetSw, destSw, routerIpPrefix); + result = rulePopulator.populateIpRuleForRouter(targetSw, routerIpPrefix, destSw, nextHops); + if (!result) { + return false; + } + } + + // Populates MPLS rules to all routers + log.debug("populateEcmpRoutingRulePartial in device{} towards {} for all MPLS rules", + targetSw, destSw); + result = rulePopulator.populateMplsRule(targetSw, destSw, nextHops); + if (!result) { + return false; + } + + return true; + } + + /** + * Populates table miss entries for all tables, and pipeline rules for VLAN + * and TACM tables. + * + * @param deviceId Switch ID to set the rules + */ + public void populateTtpRules(DeviceId deviceId) { + rulePopulator.populateTableVlan(deviceId); + rulePopulator.populateTableTMac(deviceId); + } + + /** + * Start the flow rule population process if it was never started. The + * process finishes successfully when all flow rules are set and stops with + * ABORTED status when any groups required for flows is not set yet. + */ + public void startPopulationProcess() { + statusLock.lock(); + try { + if (populationStatus == Status.IDLE + || populationStatus == Status.SUCCEEDED + || populationStatus == Status.ABORTED) { + populationStatus = Status.STARTED; + populateAllRoutingRules(); + } else { + log.warn("Not initiating startPopulationProcess as populationStatus is {}", + populationStatus); + } + } finally { + statusLock.unlock(); + } + } + + /** + * Resume the flow rule population process if it was aborted for any reason. + * Mostly the process is aborted when the groups required are not set yet. + */ + public void resumePopulationProcess() { + statusLock.lock(); + try { + if (populationStatus == Status.ABORTED) { + populationStatus = Status.STARTED; + // TODO: we need to restart from the point aborted instead of + // restarting. + populateAllRoutingRules(); + } + } finally { + statusLock.unlock(); + } + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DefaultTunnel.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DefaultTunnel.java new file mode 100644 index 00000000..8c6fbe8d --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DefaultTunnel.java @@ -0,0 +1,111 @@ +/* + * Copyright 2015 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.segmentrouting; + +import java.util.List; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Tunnel class. + */ +public class DefaultTunnel implements Tunnel { + + private final String id; + private final List<Integer> labelIds; + + private int groupId; + private boolean allowedToRemoveGroup; + + /** + * Creates a Tunnel reference. + * + * @param tid Tunnel ID + * @param labelIds Label stack of the tunnel + */ + public DefaultTunnel(String tid, List<Integer> labelIds) { + this.id = checkNotNull(tid); + this.labelIds = labelIds; + //TODO: need to register the class in Kryo for this + //this.labelIds = Collections.unmodifiableList(labelIds); + this.groupId = -1; + } + + /** + * Creates a new DefaultTunnel reference using the tunnel reference. + * + * @param tunnel DefaultTunnel reference + */ + public DefaultTunnel(DefaultTunnel tunnel) { + this.id = tunnel.id; + this.labelIds = tunnel.labelIds; + this.groupId = tunnel.groupId; + } + + @Override + public String id() { + return this.id; + } + + @Override + public List<Integer> labelIds() { + return this.labelIds; + } + + @Override + public int groupId() { + return this.groupId; + } + + @Override + public void setGroupId(int id) { + this.groupId = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o instanceof DefaultTunnel) { + DefaultTunnel tunnel = (DefaultTunnel) o; + // We compare only the tunnel paths. + if (tunnel.labelIds.equals(this.labelIds)) { + return true; + } + } + + return false; + } + + @Override + public int hashCode() { + return Objects.hash(labelIds); + } + + @Override + public boolean isAllowedToRemoveGroup() { + return this.allowedToRemoveGroup; + } + + @Override + public void allowToRemoveGroup(boolean b) { + this.allowedToRemoveGroup = b; + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DeviceConfiguration.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DeviceConfiguration.java new file mode 100644 index 00000000..d82eb5ec --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/DeviceConfiguration.java @@ -0,0 +1,404 @@ +package org.onosproject.segmentrouting; + +import com.google.common.collect.Lists; +import org.onlab.packet.Ip4Address; +import org.onlab.packet.Ip4Prefix; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MacAddress; +import org.onosproject.segmentrouting.grouphandler.DeviceProperties; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.segmentrouting.config.NetworkConfig.SwitchConfig; +import org.onosproject.segmentrouting.config.NetworkConfigManager; +import org.onosproject.segmentrouting.config.SegmentRouterConfig; +import org.onosproject.segmentrouting.config.SegmentRouterConfig.Subnet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Segment Routing configuration component that reads the + * segment routing related configuration from Network Configuration Manager + * component and organizes in more accessible formats. + * + * TODO: Merge multiple Segment Routing configuration wrapper classes into one. + */ +public class DeviceConfiguration implements DeviceProperties { + + private static final Logger log = LoggerFactory + .getLogger(DeviceConfiguration.class); + private final List<Integer> allSegmentIds = new ArrayList<Integer>(); + private final HashMap<DeviceId, SegmentRouterInfo> deviceConfigMap = new HashMap<>(); + private final NetworkConfigManager configService; + + private class SegmentRouterInfo { + int nodeSid; + DeviceId deviceId; + Ip4Address ip; + MacAddress mac; + boolean isEdge; + HashMap<PortNumber, Ip4Address> gatewayIps; + HashMap<PortNumber, Ip4Prefix> subnets; + List<SegmentRouterConfig.AdjacencySid> adjacencySids; + } + + /** + * Constructor. Reads all the configuration for all devices of type + * Segment Router and organizes into various maps for easier access. + * + * @param configService handle to network configuration manager + * component from where the relevant configuration is retrieved. + */ + public DeviceConfiguration(NetworkConfigManager configService) { + this.configService = checkNotNull(configService); + List<SwitchConfig> allSwitchCfg = + this.configService.getConfiguredAllowedSwitches(); + for (SwitchConfig cfg : allSwitchCfg) { + if (!(cfg instanceof SegmentRouterConfig)) { + continue; + } + SegmentRouterInfo info = new SegmentRouterInfo(); + info.nodeSid = ((SegmentRouterConfig) cfg).getNodeSid(); + info.deviceId = ((SegmentRouterConfig) cfg).getDpid(); + info.mac = MacAddress.valueOf((( + SegmentRouterConfig) cfg).getRouterMac()); + String routerIp = ((SegmentRouterConfig) cfg).getRouterIp(); + Ip4Prefix prefix = checkNotNull(IpPrefix.valueOf(routerIp).getIp4Prefix()); + info.ip = prefix.address(); + info.isEdge = ((SegmentRouterConfig) cfg).isEdgeRouter(); + info.subnets = new HashMap<>(); + info.gatewayIps = new HashMap<PortNumber, Ip4Address>(); + for (Subnet s: ((SegmentRouterConfig) cfg).getSubnets()) { + info.subnets.put(PortNumber.portNumber(s.getPortNo()), + Ip4Prefix.valueOf(s.getSubnetIp())); + String gatewayIp = s.getSubnetIp(). + substring(0, s.getSubnetIp().indexOf('/')); + info.gatewayIps.put(PortNumber.portNumber(s.getPortNo()), + Ip4Address.valueOf(gatewayIp)); + } + info.adjacencySids = ((SegmentRouterConfig) cfg).getAdjacencySids(); + this.deviceConfigMap.put(info.deviceId, info); + this.allSegmentIds.add(info.nodeSid); + + } + } + + /** + * Returns the segment id of a segment router. + * + * @param deviceId device identifier + * @return segment id + */ + @Override + public int getSegmentId(DeviceId deviceId) { + if (deviceConfigMap.get(deviceId) != null) { + log.debug("getSegmentId for device{} is {}", + deviceId, + deviceConfigMap.get(deviceId).nodeSid); + return deviceConfigMap.get(deviceId).nodeSid; + } else { + log.warn("getSegmentId for device {} " + + "throwing IllegalStateException " + + "because device does not exist in config", deviceId); + throw new IllegalStateException(); + } + } + + /** + * Returns the segment id of a segment router given its mac address. + * + * @param routerMac router mac address + * @return segment id + */ + public int getSegmentId(MacAddress routerMac) { + for (Map.Entry<DeviceId, SegmentRouterInfo> entry: + deviceConfigMap.entrySet()) { + if (entry.getValue().mac.equals(routerMac)) { + return entry.getValue().nodeSid; + } + } + + return -1; + } + + /** + * Returns the segment id of a segment router given its router ip address. + * + * @param routerAddress router ip address + * @return segment id + */ + public int getSegmentId(Ip4Address routerAddress) { + for (Map.Entry<DeviceId, SegmentRouterInfo> entry: + deviceConfigMap.entrySet()) { + if (entry.getValue().ip.equals(routerAddress)) { + return entry.getValue().nodeSid; + } + } + + return -1; + } + + /** + * Returns the router mac of a segment router. + * + * @param deviceId device identifier + * @return router mac address + */ + @Override + public MacAddress getDeviceMac(DeviceId deviceId) { + if (deviceConfigMap.get(deviceId) != null) { + log.debug("getDeviceMac for device{} is {}", + deviceId, + deviceConfigMap.get(deviceId).mac); + return deviceConfigMap.get(deviceId).mac; + } else { + log.warn("getDeviceMac for device {} " + + "throwing IllegalStateException " + + "because device does not exist in config", deviceId); + throw new IllegalStateException(); + } + } + + /** + * Returns the router ip address of a segment router. + * + * @param deviceId device identifier + * @return router ip address + */ + public Ip4Address getRouterIp(DeviceId deviceId) { + if (deviceConfigMap.get(deviceId) != null) { + log.debug("getDeviceIp for device{} is {}", + deviceId, + deviceConfigMap.get(deviceId).ip); + return deviceConfigMap.get(deviceId).ip; + } else { + log.warn("getRouterIp for device {} " + + "throwing IllegalStateException " + + "because device does not exist in config", deviceId); + throw new IllegalStateException(); + } + } + + /** + * Indicates if the segment router is a edge router or + * a transit/back bone router. + * + * @param deviceId device identifier + * @return boolean + */ + @Override + public boolean isEdgeDevice(DeviceId deviceId) { + if (deviceConfigMap.get(deviceId) != null) { + log.debug("isEdgeDevice for device{} is {}", + deviceId, + deviceConfigMap.get(deviceId).isEdge); + return deviceConfigMap.get(deviceId).isEdge; + } else { + log.warn("isEdgeDevice for device {} " + + "throwing IllegalStateException " + + "because device does not exist in config", deviceId); + throw new IllegalStateException(); + } + } + + /** + * Returns the segment ids of all configured segment routers. + * + * @return list of segment ids + */ + @Override + public List<Integer> getAllDeviceSegmentIds() { + return allSegmentIds; + } + + /** + * Returns the device identifier or data plane identifier (dpid) + * of a segment router given its segment id. + * + * @param sid segment id + * @return deviceId device identifier + */ + public DeviceId getDeviceId(int sid) { + for (Map.Entry<DeviceId, SegmentRouterInfo> entry: + deviceConfigMap.entrySet()) { + if (entry.getValue().nodeSid == sid) { + return entry.getValue().deviceId; + } + } + + return null; + } + + /** + * Returns the device identifier or data plane identifier (dpid) + * of a segment router given its router ip address. + * + * @param ipAddress router ip address + * @return deviceId device identifier + */ + public DeviceId getDeviceId(Ip4Address ipAddress) { + for (Map.Entry<DeviceId, SegmentRouterInfo> entry: + deviceConfigMap.entrySet()) { + if (entry.getValue().ip.equals(ipAddress)) { + return entry.getValue().deviceId; + } + } + + return null; + } + + /** + * Returns the configured subnet gateway ip addresses for a segment router. + * + * @param deviceId device identifier + * @return list of ip addresses + */ + public List<Ip4Address> getSubnetGatewayIps(DeviceId deviceId) { + if (deviceConfigMap.get(deviceId) != null) { + log.debug("getSubnetGatewayIps for device{} is {}", + deviceId, + deviceConfigMap.get(deviceId).gatewayIps.values()); + return new ArrayList<Ip4Address>(deviceConfigMap. + get(deviceId).gatewayIps.values()); + } else { + return null; + } + } + + /** + * Returns the configured subnet prefixes for a segment router. + * + * @param deviceId device identifier + * @return list of ip prefixes + */ + public List<Ip4Prefix> getSubnets(DeviceId deviceId) { + if (deviceConfigMap.get(deviceId) != null) { + log.debug("getSubnets for device{} is {}", + deviceId, + deviceConfigMap.get(deviceId).subnets.values()); + return new ArrayList<Ip4Prefix>(deviceConfigMap. + get(deviceId).subnets.values()); + } else { + return null; + } + } + + /** + * Returns the router ip address of segment router that has the + * specified ip address in its subnets. + * + * @param destIpAddress target ip address + * @return router ip address + */ + public Ip4Address getRouterIpAddressForASubnetHost(Ip4Address destIpAddress) { + for (Map.Entry<DeviceId, SegmentRouterInfo> entry: + deviceConfigMap.entrySet()) { + for (Ip4Prefix prefix:entry.getValue().subnets.values()) { + if (prefix.contains(destIpAddress)) { + return entry.getValue().ip; + } + } + } + + log.debug("No router was found for {}", destIpAddress); + return null; + } + + /** + * Returns the router mac address of segment router that has the + * specified ip address as one of its subnet gateway ip address. + * + * @param gatewayIpAddress router gateway ip address + * @return router mac address + */ + public MacAddress getRouterMacForAGatewayIp(Ip4Address gatewayIpAddress) { + for (Map.Entry<DeviceId, SegmentRouterInfo> entry: + deviceConfigMap.entrySet()) { + if (entry.getValue().gatewayIps. + values().contains(gatewayIpAddress)) { + return entry.getValue().mac; + } + } + + log.debug("Cannot find a router for {}", gatewayIpAddress); + return null; + } + + + /** + * Checks if the host is in the subnet defined in the router with the + * device ID given. + * + * @param deviceId device identification of the router + * @param hostIp host IP address to check + * @return true if the host is within the subnet of the router, + * false if no subnet is defined under the router or if the host is not + * within the subnet defined in the router + */ + public boolean inSameSubnet(DeviceId deviceId, Ip4Address hostIp) { + + List<Ip4Prefix> subnets = getSubnets(deviceId); + if (subnets == null) { + return false; + } + + for (Ip4Prefix subnet: subnets) { + if (subnet.contains(hostIp)) { + return true; + } + } + + return false; + } + + /** + * Returns the ports corresponding to the adjacency Sid given. + * + * @param deviceId device identification of the router + * @param sid adjacency Sid + * @return list of port numbers + */ + public List<Integer> getPortsForAdjacencySid(DeviceId deviceId, int sid) { + if (deviceConfigMap.get(deviceId) != null) { + for (SegmentRouterConfig.AdjacencySid asid : deviceConfigMap.get(deviceId).adjacencySids) { + if (asid.getAdjSid() == sid) { + return asid.getPorts(); + } + } + } + + return Lists.newArrayList(); + } + + /** + * Check if the Sid given is whether adjacency Sid of the router device or not. + * + * @param deviceId device identification of the router + * @param sid Sid to check + * @return true if the Sid given is the adjacency Sid of the device, + * otherwise false + */ + public boolean isAdjacencySid(DeviceId deviceId, int sid) { + if (deviceConfigMap.get(deviceId) != null) { + if (deviceConfigMap.get(deviceId).adjacencySids.isEmpty()) { + return false; + } else { + for (SegmentRouterConfig.AdjacencySid asid: + deviceConfigMap.get(deviceId).adjacencySids) { + if (asid.getAdjSid() == sid) { + return true; + } + } + return false; + } + } + + return false; + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ECMPShortestPathGraph.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ECMPShortestPathGraph.java new file mode 100644 index 00000000..cd28fcf9 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/ECMPShortestPathGraph.java @@ -0,0 +1,374 @@ +/* + * Copyright 2015 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.segmentrouting; + +import org.onosproject.net.DefaultLink; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.provider.ProviderId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +/** + * This class creates bandwidth constrained breadth first tree and returns paths + * from root Device to leaf Devicees which satisfies the bandwidth condition. If + * bandwidth parameter is not specified, the normal breadth first tree will be + * calculated. The paths are snapshot paths at the point of the class + * instantiation. + */ +public class ECMPShortestPathGraph { + LinkedList<DeviceId> deviceQueue = new LinkedList<>(); + LinkedList<Integer> distanceQueue = new LinkedList<>(); + HashMap<DeviceId, Integer> deviceSearched = new HashMap<>(); + HashMap<DeviceId, ArrayList<Link>> upstreamLinks = new HashMap<>(); + HashMap<DeviceId, ArrayList<Path>> paths = new HashMap<>(); + HashMap<Integer, ArrayList<DeviceId>> distanceDeviceMap = new HashMap<>(); + DeviceId rootDevice; + private SegmentRoutingManager srManager; + private static final Logger log = LoggerFactory + .getLogger(ECMPShortestPathGraph.class); + + /** + * Constructor. + * + * @param rootDevice root of the BFS tree + * @param linkListToAvoid link list to avoid + * @param deviceIdListToAvoid device list to avoid + */ + public ECMPShortestPathGraph(DeviceId rootDevice, List<String> deviceIdListToAvoid, + List<Link> linkListToAvoid) { + this.rootDevice = rootDevice; + calcECMPShortestPathGraph(deviceIdListToAvoid, linkListToAvoid); + } + + /** + * Constructor. + * + * @param rootDevice root of the BFS tree + * @param srManager SegmentRoutingManager object + */ + public ECMPShortestPathGraph(DeviceId rootDevice, SegmentRoutingManager srManager) { + this.rootDevice = rootDevice; + this.srManager = srManager; + calcECMPShortestPathGraph(); + } + + /** + * Calculates the BFS tree using any provided constraints and Intents. + */ + private void calcECMPShortestPathGraph() { + deviceQueue.add(rootDevice); + int currDistance = 0; + distanceQueue.add(currDistance); + deviceSearched.put(rootDevice, currDistance); + while (!deviceQueue.isEmpty()) { + DeviceId sw = deviceQueue.poll(); + DeviceId prevSw = null; + currDistance = distanceQueue.poll(); + + for (Link link : srManager.linkService.getDeviceEgressLinks(sw)) { + DeviceId reachedDevice = link.dst().deviceId(); + if ((prevSw != null) + && (prevSw.equals(reachedDevice))) { + /* Ignore LAG links between the same set of Devicees */ + continue; + } else { + prevSw = reachedDevice; + } + + Integer distance = deviceSearched.get(reachedDevice); + if ((distance != null) && (distance.intValue() < (currDistance + 1))) { + continue; + } + if (distance == null) { + /* First time visiting this Device node */ + deviceQueue.add(reachedDevice); + distanceQueue.add(currDistance + 1); + deviceSearched.put(reachedDevice, currDistance + 1); + + ArrayList<DeviceId> distanceSwArray = distanceDeviceMap + .get(currDistance + 1); + if (distanceSwArray == null) { + distanceSwArray = new ArrayList<DeviceId>(); + distanceSwArray.add(reachedDevice); + distanceDeviceMap.put(currDistance + 1, distanceSwArray); + } else { + distanceSwArray.add(reachedDevice); + } + } + + ArrayList<Link> upstreamLinkArray = + upstreamLinks.get(reachedDevice); + if (upstreamLinkArray == null) { + upstreamLinkArray = new ArrayList<Link>(); + upstreamLinkArray.add(copyDefaultLink(link)); + //upstreamLinkArray.add(link); + upstreamLinks.put(reachedDevice, upstreamLinkArray); + } else { + /* ECMP links */ + upstreamLinkArray.add(copyDefaultLink(link)); + } + } + } + } + + /** + * Calculates the BFS tree using any provided constraints and Intents. + */ + private void calcECMPShortestPathGraph(List<String> deviceIdListToAvoid, List<Link> linksToAvoid) { + deviceQueue.add(rootDevice); + int currDistance = 0; + distanceQueue.add(currDistance); + deviceSearched.put(rootDevice, currDistance); + boolean foundLinkToAvoid = false; + while (!deviceQueue.isEmpty()) { + DeviceId sw = deviceQueue.poll(); + DeviceId prevSw = null; + currDistance = distanceQueue.poll(); + for (Link link : srManager.linkService.getDeviceEgressLinks(sw)) { + for (Link linkToAvoid: linksToAvoid) { + // TODO: equls should work + //if (link.equals(linkToAvoid)) { + if (linkContains(link, linksToAvoid)) { + foundLinkToAvoid = true; + break; + } + } + if (foundLinkToAvoid) { + foundLinkToAvoid = false; + continue; + } + DeviceId reachedDevice = link.dst().deviceId(); + if (deviceIdListToAvoid.contains(reachedDevice.toString())) { + continue; + } + if ((prevSw != null) + && (prevSw.equals(reachedDevice))) { + /* Ignore LAG links between the same set of Devicees */ + continue; + } else { + prevSw = reachedDevice; + } + + Integer distance = deviceSearched.get(reachedDevice); + if ((distance != null) && (distance.intValue() < (currDistance + 1))) { + continue; + } + if (distance == null) { + /* First time visiting this Device node */ + deviceQueue.add(reachedDevice); + distanceQueue.add(currDistance + 1); + deviceSearched.put(reachedDevice, currDistance + 1); + + ArrayList<DeviceId> distanceSwArray = distanceDeviceMap + .get(currDistance + 1); + if (distanceSwArray == null) { + distanceSwArray = new ArrayList<DeviceId>(); + distanceSwArray.add(reachedDevice); + distanceDeviceMap.put(currDistance + 1, distanceSwArray); + } else { + distanceSwArray.add(reachedDevice); + } + } + + ArrayList<Link> upstreamLinkArray = + upstreamLinks.get(reachedDevice); + if (upstreamLinkArray == null) { + upstreamLinkArray = new ArrayList<Link>(); + upstreamLinkArray.add(copyDefaultLink(link)); + upstreamLinks.put(reachedDevice, upstreamLinkArray); + } else { + /* ECMP links */ + upstreamLinkArray.add(copyDefaultLink(link)); + } + } + } + } + + + private boolean linkContains(Link link, List<Link> links) { + + DeviceId srcDevice1 = link.src().deviceId(); + DeviceId dstDevice1 = link.dst().deviceId(); + long srcPort1 = link.src().port().toLong(); + long dstPort1 = link.dst().port().toLong(); + + for (Link link2: links) { + DeviceId srcDevice2 = link2.src().deviceId(); + DeviceId dstDevice2 = link2.dst().deviceId(); + long srcPort2 = link2.src().port().toLong(); + long dstPort2 = link2.dst().port().toLong(); + + if (srcDevice1.toString().equals(srcDevice2.toString()) + && dstDevice1.toString().equals(dstDevice2.toString()) + && srcPort1 == srcPort2 && dstPort1 == dstPort2) { + return true; + } + } + + return false; + } + + private void getDFSPaths(DeviceId dstDeviceDeviceId, Path path, ArrayList<Path> paths) { + DeviceId rootDeviceDeviceId = rootDevice; + for (Link upstreamLink : upstreamLinks.get(dstDeviceDeviceId)) { + /* Deep clone the path object */ + Path sofarPath; + ArrayList<Link> sofarLinks = new ArrayList<Link>(); + if (path != null && !path.links().isEmpty()) { + sofarLinks.addAll(path.links()); + } + sofarLinks.add(upstreamLink); + sofarPath = new DefaultPath(ProviderId.NONE, sofarLinks, 0); + if (upstreamLink.src().deviceId().equals(rootDeviceDeviceId)) { + paths.add(sofarPath); + return; + } else { + getDFSPaths(upstreamLink.src().deviceId(), sofarPath, paths); + } + } + } + + /** + * Return root Device for the graph. + * + * @return root Device + */ + public DeviceId getRootDevice() { + return rootDevice; + } + + /** + * Return the computed ECMP paths from the root Device to a given Device in + * the network. + * + * @param targetDevice the target Device + * @return the list of ECMP Paths from the root Device to the target Device + */ + public ArrayList<Path> getECMPPaths(DeviceId targetDevice) { + ArrayList<Path> pathArray = paths.get(targetDevice); + if (pathArray == null && deviceSearched.containsKey( + targetDevice)) { + pathArray = new ArrayList<>(); + DeviceId sw = targetDevice; + getDFSPaths(sw, null, pathArray); + paths.put(targetDevice, pathArray); + } + return pathArray; + } + + /** + * Return the complete info of the computed ECMP paths for each Device + * learned in multiple iterations from the root Device. + * + * @return the hash table of Devicees learned in multiple Dijkstra + * iterations and corresponding ECMP paths to it from the root + * Device + */ + public HashMap<Integer, HashMap<DeviceId, + ArrayList<Path>>> getCompleteLearnedDeviceesAndPaths() { + + HashMap<Integer, HashMap<DeviceId, ArrayList<Path>>> pathGraph = new + HashMap<Integer, HashMap<DeviceId, ArrayList<Path>>>(); + + for (Integer itrIndx : distanceDeviceMap.keySet()) { + HashMap<DeviceId, ArrayList<Path>> swMap = new + HashMap<DeviceId, ArrayList<Path>>(); + for (DeviceId sw : distanceDeviceMap.get(itrIndx)) { + swMap.put(sw, getECMPPaths(sw)); + } + pathGraph.put(itrIndx, swMap); + } + + return pathGraph; + } + + /** + * Return the complete info of the computed ECMP paths for each Device + * learned in multiple iterations from the root Device. + * + * @return the hash table of Devicees learned in multiple Dijkstra + * iterations and corresponding ECMP paths in terms of Devicees to + * be traversed to it from the root Device + */ + public HashMap<Integer, HashMap<DeviceId, + ArrayList<ArrayList<DeviceId>>>> getAllLearnedSwitchesAndVia() { + + HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> deviceViaMap = + new HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>>(); + + for (Integer itrIndx : distanceDeviceMap.keySet()) { + HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swMap = + new HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>(); + + for (DeviceId sw : distanceDeviceMap.get(itrIndx)) { + ArrayList<ArrayList<DeviceId>> swViaArray = new ArrayList<>(); + for (Path path : getECMPPaths(sw)) { + ArrayList<DeviceId> swVia = new ArrayList<>(); + for (Link link : path.links()) { + if (link.src().deviceId().equals(rootDevice)) { + /* No need to add the root Device again in + * the Via list + */ + continue; + } + swVia.add(link.src().deviceId()); + } + swViaArray.add(swVia); + } + swMap.put(sw, swViaArray); + } + deviceViaMap.put(itrIndx, swMap); + } + return deviceViaMap; + } + + + private Link copyDefaultLink(Link link) { + DefaultLink src = (DefaultLink) link; + DefaultLink defaultLink = new DefaultLink(src.providerId(), src.src(), + src.dst(), src.type(), src.annotations()); + + return defaultLink; + } + + @Override + public String toString() { + StringBuilder sBuilder = new StringBuilder(); + for (Device device: srManager.deviceService.getDevices()) { + if (device.id() != rootDevice) { + sBuilder.append("Paths from" + rootDevice + " to " + device.id() + "\r\n"); + ArrayList<Path> paths = getECMPPaths(device.id()); + if (paths != null) { + for (Path path : paths) { + for (Link link : path.links()) { + sBuilder.append(" : " + link.src() + " -> " + link.dst()); + } + } + } + } + } + return sBuilder.toString(); + } +} + diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java new file mode 100644 index 00000000..0f8fa59d --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IcmpHandler.java @@ -0,0 +1,171 @@ +/* + * Copyright 2015 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.segmentrouting; + +import org.onlab.packet.Ethernet; +import org.onlab.packet.ICMP; +import org.onlab.packet.IPv4; +import org.onlab.packet.Ip4Address; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MPLS; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.packet.DefaultOutboundPacket; +import org.onosproject.net.packet.InboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class IcmpHandler { + + private static Logger log = LoggerFactory.getLogger(IcmpHandler.class); + private SegmentRoutingManager srManager; + private DeviceConfiguration config; + + /** + * Creates an IcmpHandler object. + * + * @param srManager SegmentRoutingManager object + */ + public IcmpHandler(SegmentRoutingManager srManager) { + this.srManager = srManager; + this.config = checkNotNull(srManager.deviceConfiguration); + } + + /** + * Process incoming ICMP packet. + * If it is an ICMP request to router or known host, then sends an ICMP response. + * If it is an ICMP packet to known host and forward the packet to the host. + * If it is an ICMP packet to unknown host in a subnet, then sends an ARP request + * to the subnet. + * + * @param pkt inbound packet + */ + public void processPacketIn(InboundPacket pkt) { + + Ethernet ethernet = pkt.parsed(); + IPv4 ipv4 = (IPv4) ethernet.getPayload(); + + ConnectPoint connectPoint = pkt.receivedFrom(); + DeviceId deviceId = connectPoint.deviceId(); + Ip4Address destinationAddress = + Ip4Address.valueOf(ipv4.getDestinationAddress()); + List<Ip4Address> gatewayIpAddresses = config.getSubnetGatewayIps(deviceId); + Ip4Address routerIp = config.getRouterIp(deviceId); + IpPrefix routerIpPrefix = IpPrefix.valueOf(routerIp, IpPrefix.MAX_INET_MASK_LENGTH); + Ip4Address routerIpAddress = routerIpPrefix.getIp4Prefix().address(); + + // ICMP to the router IP or gateway IP + if (((ICMP) ipv4.getPayload()).getIcmpType() == ICMP.TYPE_ECHO_REQUEST && + (destinationAddress.equals(routerIpAddress) || + gatewayIpAddresses.contains(destinationAddress))) { + sendICMPResponse(ethernet, connectPoint); + // TODO: do we need to set the flow rule again ?? + + // ICMP for any known host + } else if (!srManager.hostService.getHostsByIp(destinationAddress).isEmpty()) { + srManager.ipHandler.forwardPackets(deviceId, destinationAddress); + + // ICMP for an unknown host in the subnet of the router + } else if (config.inSameSubnet(deviceId, destinationAddress)) { + srManager.arpHandler.sendArpRequest(deviceId, destinationAddress, connectPoint); + + // ICMP for an unknown host + } else { + log.debug("ICMP request for unknown host {} ", destinationAddress); + // Do nothing + } + } + + private void sendICMPResponse(Ethernet icmpRequest, ConnectPoint outport) { + + Ethernet icmpReplyEth = new Ethernet(); + + IPv4 icmpRequestIpv4 = (IPv4) icmpRequest.getPayload(); + IPv4 icmpReplyIpv4 = new IPv4(); + + int destAddress = icmpRequestIpv4.getDestinationAddress(); + icmpReplyIpv4.setDestinationAddress(icmpRequestIpv4.getSourceAddress()); + icmpReplyIpv4.setSourceAddress(destAddress); + icmpReplyIpv4.setTtl((byte) 64); + icmpReplyIpv4.setChecksum((short) 0); + + ICMP icmpReply = new ICMP(); + icmpReply.setIcmpType(ICMP.TYPE_ECHO_REPLY); + icmpReply.setIcmpCode(ICMP.SUBTYPE_ECHO_REPLY); + icmpReply.setChecksum((short) 0); + + icmpReplyIpv4.setPayload(icmpReply); + + icmpReplyEth.setPayload(icmpReplyIpv4); + icmpReplyEth.setEtherType(Ethernet.TYPE_IPV4); + icmpReplyEth.setDestinationMACAddress(icmpRequest.getSourceMACAddress()); + icmpReplyEth.setSourceMACAddress(icmpRequest.getDestinationMACAddress()); + icmpReplyEth.setVlanID(icmpRequest.getVlanID()); + + Ip4Address destIpAddress = Ip4Address.valueOf(icmpReplyIpv4.getDestinationAddress()); + Ip4Address destRouterAddress = config.getRouterIpAddressForASubnetHost(destIpAddress); + int sid = config.getSegmentId(destRouterAddress); + if (sid < 0) { + log.warn("Cannot find the Segment ID for {}", destAddress); + return; + } + + sendPacketOut(outport, icmpReplyEth, sid); + + } + + private void sendPacketOut(ConnectPoint outport, Ethernet payload, int sid) { + + IPv4 ipPacket = (IPv4) payload.getPayload(); + Ip4Address destIpAddress = Ip4Address.valueOf(ipPacket.getDestinationAddress()); + + if (sid == -1 || config.getSegmentId(payload.getDestinationMAC()) == sid || + config.inSameSubnet(outport.deviceId(), destIpAddress)) { + TrafficTreatment treatment = DefaultTrafficTreatment.builder(). + setOutput(outport.port()).build(); + OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(), + treatment, ByteBuffer.wrap(payload.serialize())); + srManager.packetService.emit(packet); + } else { + log.warn("Send a MPLS packet as a ICMP response"); + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(outport.port()) + .build(); + + payload.setEtherType(Ethernet.MPLS_UNICAST); + MPLS mplsPkt = new MPLS(); + mplsPkt.setLabel(sid); + mplsPkt.setTtl(((IPv4) payload.getPayload()).getTtl()); + mplsPkt.setPayload(payload.getPayload()); + payload.setPayload(mplsPkt); + + OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(), + treatment, ByteBuffer.wrap(payload.serialize())); + + srManager.packetService.emit(packet); + } + } + + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java new file mode 100644 index 00000000..81d00f50 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/IpHandler.java @@ -0,0 +1,152 @@ +/* + * Copyright 2015 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.segmentrouting; + +import org.onlab.packet.Ethernet; +import org.onlab.packet.IPv4; +import org.onlab.packet.Ip4Address; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Host; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.packet.DefaultOutboundPacket; +import org.onosproject.net.packet.InboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class IpHandler { + + private static Logger log = LoggerFactory.getLogger(IpHandler.class); + private SegmentRoutingManager srManager; + private DeviceConfiguration config; + private ConcurrentHashMap<Ip4Address, ConcurrentLinkedQueue<IPv4>> ipPacketQueue; + + /** + * Creates an IpHandler object. + * + * @param srManager SegmentRoutingManager object + */ + public IpHandler(SegmentRoutingManager srManager) { + this.srManager = srManager; + this.config = checkNotNull(srManager.deviceConfiguration); + ipPacketQueue = new ConcurrentHashMap<Ip4Address, ConcurrentLinkedQueue<IPv4>>(); + } + + /** + * Processes incoming IP packets. + * + * If it is an IP packet for known host, then forward it to the host. + * If it is an IP packet for unknown host in subnet, then send an ARP request + * to the subnet. + * + * @param pkt incoming packet + */ + public void processPacketIn(InboundPacket pkt) { + Ethernet ethernet = pkt.parsed(); + IPv4 ipv4 = (IPv4) ethernet.getPayload(); + + ConnectPoint connectPoint = pkt.receivedFrom(); + DeviceId deviceId = connectPoint.deviceId(); + Ip4Address destinationAddress = + Ip4Address.valueOf(ipv4.getDestinationAddress()); + + // IP packet for know hosts + if (!srManager.hostService.getHostsByIp(destinationAddress).isEmpty()) { + forwardPackets(deviceId, destinationAddress); + + // IP packet for unknown host in the subnet of the router + } else if (config.inSameSubnet(deviceId, destinationAddress)) { + srManager.arpHandler.sendArpRequest(deviceId, destinationAddress, connectPoint); + + // IP packets for unknown host + } else { + log.debug("ICMP request for unknown host {} which is not in the subnet", + destinationAddress); + // Do nothing + } + } + + /** + * Adds the IP packet to a buffer. + * The packets are forwarded to corresponding destination when the destination + * MAC address is known via ARP response. + * + * @param ipPacket IP packet to add to the buffer + */ + public void addToPacketBuffer(IPv4 ipPacket) { + + // Better not buffer TPC packets due to out-of-order packet transfer + if (ipPacket.getProtocol() == IPv4.PROTOCOL_TCP) { + return; + } + + Ip4Address destIpAddress = Ip4Address.valueOf(ipPacket.getDestinationAddress()); + + if (ipPacketQueue.get(destIpAddress) == null) { + ConcurrentLinkedQueue<IPv4> queue = new ConcurrentLinkedQueue<IPv4>(); + queue.add(ipPacket); + ipPacketQueue.put(destIpAddress, queue); + } else { + ipPacketQueue.get(destIpAddress).add(ipPacket); + } + } + + /** + * Forwards IP packets in the buffer to the destination IP address. + * It is called when the controller finds the destination MAC address + * via ARP responsees. + * + * @param deviceId switch device ID + * @param destIpAddress destination IP address + */ + public void forwardPackets(DeviceId deviceId, Ip4Address destIpAddress) { + if (ipPacketQueue.get(destIpAddress) == null) { + return; + } + + for (IPv4 ipPacket : ipPacketQueue.get(destIpAddress)) { + Ip4Address destAddress = Ip4Address.valueOf(ipPacket.getDestinationAddress()); + if (ipPacket != null && config.inSameSubnet(deviceId, destAddress)) { + ipPacket.setTtl((byte) (ipPacket.getTtl() - 1)); + ipPacket.setChecksum((short) 0); + for (Host dest: srManager.hostService.getHostsByIp(destIpAddress)) { + Ethernet eth = new Ethernet(); + eth.setDestinationMACAddress(dest.mac()); + eth.setSourceMACAddress(config.getDeviceMac(deviceId)); + eth.setEtherType(Ethernet.TYPE_IPV4); + eth.setPayload(ipPacket); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder(). + setOutput(dest.location().port()).build(); + OutboundPacket packet = new DefaultOutboundPacket(deviceId, + treatment, ByteBuffer.wrap(eth.serialize())); + srManager.packetService.emit(packet); + ipPacketQueue.get(destIpAddress).remove(ipPacket); + } + ipPacketQueue.get(destIpAddress).remove(ipPacket); + } + } + } + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/NetworkConfigHandler.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/NetworkConfigHandler.java new file mode 100644 index 00000000..d3468178 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/NetworkConfigHandler.java @@ -0,0 +1,157 @@ +/* + * Copyright 2015 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.segmentrouting; + +import com.google.common.collect.Lists; + +import org.onlab.packet.Ip4Address; +import org.onlab.packet.Ip4Prefix; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MacAddress; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.PortNumber; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Set; + +/** + * This class is temporary class and used only for test. + * It will be replaced with "real" Network Config Manager. + * + * TODO: Knock off this wrapper and directly use DeviceConfiguration class + */ + +public class NetworkConfigHandler { + + private static Logger log = LoggerFactory.getLogger(NetworkConfigHandler.class); + private SegmentRoutingManager srManager; + private DeviceConfiguration deviceConfig; + + public NetworkConfigHandler(SegmentRoutingManager srManager, + DeviceConfiguration deviceConfig) { + this.srManager = srManager; + this.deviceConfig = deviceConfig; + } + + public List<Ip4Address> getGatewayIpAddress(DeviceId deviceId) { + return this.deviceConfig.getSubnetGatewayIps(deviceId); + } + + public IpPrefix getRouterIpAddress(DeviceId deviceId) { + return IpPrefix.valueOf(deviceConfig.getRouterIp(deviceId), 32); + } + + public MacAddress getRouterMacAddress(DeviceId deviceId) { + return deviceConfig.getDeviceMac(deviceId); + } + + public boolean inSameSubnet(DeviceId deviceId, Ip4Address destIp) { + + List<Ip4Prefix> subnets = getSubnetInfo(deviceId); + if (subnets == null) { + return false; + } + + return subnets.stream() + .anyMatch((subnet) -> subnet.contains(destIp)); + } + + public boolean inSameSubnet(Ip4Address address, int sid) { + DeviceId deviceId = deviceConfig.getDeviceId(sid); + if (deviceId == null) { + log.warn("Cannot find a device for SID {}", sid); + return false; + } + + return inSameSubnet(deviceId, address); + } + + public List<Ip4Prefix> getSubnetInfo(DeviceId deviceId) { + return deviceConfig.getSubnets(deviceId); + } + + public int getMplsId(DeviceId deviceId) { + return deviceConfig.getSegmentId(deviceId); + } + + public int getMplsId(MacAddress routerMac) { + return deviceConfig.getSegmentId(routerMac); + } + + public int getMplsId(Ip4Address routerIpAddress) { + return deviceConfig.getSegmentId(routerIpAddress); + } + + public boolean isEcmpNotSupportedInTransit(DeviceId deviceId) { + //TODO: temporarily changing to true to test with Dell + return true; + } + + public boolean isTransitRouter(DeviceId deviceId) { + return !(deviceConfig.isEdgeDevice(deviceId)); + } + + + public boolean isEdgeRouter(DeviceId deviceId) { + return deviceConfig.isEdgeDevice(deviceId); + } + + private List<PortNumber> getPortsToNeighbors(DeviceId deviceId, List<DeviceId> fwdSws) { + + List<PortNumber> portNumbers = Lists.newArrayList(); + + Set<Link> links = srManager.linkService.getDeviceEgressLinks(deviceId); + for (Link link: links) { + for (DeviceId swId: fwdSws) { + if (link.dst().deviceId().equals(swId)) { + portNumbers.add(link.src().port()); + break; + } + } + } + + return portNumbers; + } + + public List<PortNumber> getPortsToDevice(DeviceId deviceId) { + List<PortNumber> portNumbers = Lists.newArrayList(); + + Set<Link> links = srManager.linkService.getDeviceEgressLinks(deviceId); + for (Link link: links) { + if (link.dst().deviceId().equals(deviceId)) { + portNumbers.add(link.src().port()); + } + } + + return portNumbers; + } + + + public Ip4Address getDestinationRouterAddress(Ip4Address destIpAddress) { + return deviceConfig.getRouterIpAddressForASubnetHost(destIpAddress); + } + + public DeviceId getDeviceId(Ip4Address ip4Address) { + return deviceConfig.getDeviceId(ip4Address); + } + + public MacAddress getRouterMac(Ip4Address targetAddress) { + return deviceConfig.getRouterMacForAGatewayIp(targetAddress); + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/Policy.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/Policy.java new file mode 100644 index 00000000..2e417959 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/Policy.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015 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.segmentrouting; + +/** + * Interface for Segment Routing Policy. + */ +public interface Policy { + /** + * Enums for policy type. + */ + enum Type { + // Tunnel flow policy type + TUNNEL_FLOW, + + // Load balancing policy type + LOADBALANCE, + + // policy to avoid specific routers or links + AVOID, + + // Access Control policy type + DENY + } + + /** + * Returns the policy ID. + * + * @return policy ID + */ + String id(); + + /** + * Returns the priority of the policy. + * + * @return priority + */ + int priority(); + + /** + * Returns the policy type. + * + * @return policy type + */ + Type type(); + + /** + * Returns the source IP address of the policy. + * + * @return source IP address + */ + String srcIp(); + + /** + * Returns the destination IP address of the policy. + * + * @return destination IP address + */ + String dstIp(); + + /** + * Returns the IP protocol of the policy. + * + * @return IP protocol + */ + String ipProto(); + + /** + * Returns the source port of the policy. + * + * @return source port + */ + short srcPort(); + + /** + * Returns the destination of the policy. + * + * @return destination port + */ + short dstPort(); + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/PolicyHandler.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/PolicyHandler.java new file mode 100644 index 00000000..83cb7e86 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/PolicyHandler.java @@ -0,0 +1,215 @@ +/* + * Copyright 2015 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.segmentrouting; + +import org.onlab.packet.Ethernet; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.TpPort; +import org.onosproject.cli.net.IpProtocol; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flowobjective.DefaultForwardingObjective; +import org.onosproject.net.flowobjective.FlowObjectiveService; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.store.service.EventuallyConsistentMap; +import org.slf4j.Logger; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Segment Routing Policy Handler. + */ +public class PolicyHandler { + + protected final Logger log = getLogger(getClass()); + + private ApplicationId appId; + private DeviceConfiguration deviceConfiguration; + private FlowObjectiveService flowObjectiveService; + private TunnelHandler tunnelHandler; + private final EventuallyConsistentMap<String, Policy> policyStore; + + public enum Result { + SUCCESS, + POLICY_EXISTS, + ID_EXISTS, + TUNNEL_NOT_FOUND, + POLICY_NOT_FOUND, + UNSUPPORTED_TYPE + } + + /** + * Creates a reference. + * + * @param appId segment routing application ID + * @param deviceConfiguration DeviceConfiguration reference + * @param flowObjectiveService FlowObjectiveService reference + * @param tunnelHandler tunnel handler reference + * @param policyStore policy store + */ + public PolicyHandler(ApplicationId appId, + DeviceConfiguration deviceConfiguration, + FlowObjectiveService flowObjectiveService, + TunnelHandler tunnelHandler, + EventuallyConsistentMap<String, Policy> policyStore) { + this.appId = appId; + this.deviceConfiguration = deviceConfiguration; + this.flowObjectiveService = flowObjectiveService; + this.tunnelHandler = tunnelHandler; + this.policyStore = policyStore; + } + + /** + * Returns the policies. + * + * @return policy list + */ + public List<Policy> getPolicies() { + return policyStore.values() + .stream() + .filter(policy -> policy instanceof TunnelPolicy) + .map(policy -> new TunnelPolicy((TunnelPolicy) policy)) + .collect(Collectors.toList()); + } + + /** + * Creates a policy using the policy information given. + * @param policy policy reference to create + * @return ID_EXISTS if the same policy ID exists, + * POLICY_EXISTS if the same policy exists, TUNNEL_NOT_FOUND if the tunnel + * does not exists, UNSUPPORTED_TYPE if the policy type is not supported, + * SUCCESS if the policy is created successfully + */ + public Result createPolicy(Policy policy) { + + if (policyStore.containsKey(policy.id())) { + log.warn("The policy id {} exists already", policy.id()); + return Result.ID_EXISTS; + } + + if (policyStore.containsValue(policy)) { + log.warn("The same policy exists already"); + return Result.POLICY_EXISTS; + } + + if (policy.type() == Policy.Type.TUNNEL_FLOW) { + + TunnelPolicy tunnelPolicy = (TunnelPolicy) policy; + Tunnel tunnel = tunnelHandler.getTunnel(tunnelPolicy.tunnelId()); + if (tunnel == null) { + return Result.TUNNEL_NOT_FOUND; + } + + ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective + .builder() + .fromApp(appId) + .makePermanent() + .nextStep(tunnel.groupId()) + .withPriority(tunnelPolicy.priority()) + .withSelector(buildSelector(policy)) + .withFlag(ForwardingObjective.Flag.VERSATILE); + + DeviceId source = deviceConfiguration.getDeviceId(tunnel.labelIds().get(0)); + flowObjectiveService.forward(source, fwdBuilder.add()); + + } else { + log.warn("Policy type {} is not supported yet.", policy.type()); + return Result.UNSUPPORTED_TYPE; + } + + policyStore.put(policy.id(), policy); + + return Result.SUCCESS; + } + + /** + * Removes the policy given. + * + * @param policyInfo policy information to remove + * @return POLICY_NOT_FOUND if the policy to remove does not exists, + * SUCCESS if it is removed successfully + */ + public Result removePolicy(Policy policyInfo) { + + if (policyStore.get(policyInfo.id()) != null) { + Policy policy = policyStore.get(policyInfo.id()); + if (policy.type() == Policy.Type.TUNNEL_FLOW) { + TunnelPolicy tunnelPolicy = (TunnelPolicy) policy; + Tunnel tunnel = tunnelHandler.getTunnel(tunnelPolicy.tunnelId()); + + ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective + .builder() + .fromApp(appId) + .makePermanent() + .withSelector(buildSelector(policy)) + .withPriority(tunnelPolicy.priority()) + .nextStep(tunnel.groupId()) + .withFlag(ForwardingObjective.Flag.VERSATILE); + + DeviceId source = deviceConfiguration.getDeviceId(tunnel.labelIds().get(0)); + flowObjectiveService.forward(source, fwdBuilder.remove()); + + policyStore.remove(policyInfo.id()); + } + } else { + log.warn("Policy {} was not found", policyInfo.id()); + return Result.POLICY_NOT_FOUND; + } + + return Result.SUCCESS; + } + + + private TrafficSelector buildSelector(Policy policy) { + + TrafficSelector.Builder tsb = DefaultTrafficSelector.builder(); + tsb.matchEthType(Ethernet.TYPE_IPV4); + if (policy.dstIp() != null && !policy.dstIp().isEmpty()) { + tsb.matchIPDst(IpPrefix.valueOf(policy.dstIp())); + } + if (policy.srcIp() != null && !policy.srcIp().isEmpty()) { + tsb.matchIPSrc(IpPrefix.valueOf(policy.srcIp())); + } + if (policy.ipProto() != null && !policy.ipProto().isEmpty()) { + Short ipProto = IpProtocol.valueOf(policy.ipProto()).value(); + tsb.matchIPProtocol(ipProto.byteValue()); + if (IpProtocol.valueOf(policy.ipProto()).equals(IpProtocol.TCP)) { + if (policy.srcPort() != 0) { + tsb.matchTcpSrc(TpPort.tpPort(policy.srcPort())); + } + if (policy.dstPort() != 0) { + tsb.matchTcpDst(TpPort.tpPort(policy.dstPort())); + } + } else if (IpProtocol.valueOf(policy.ipProto()).equals(IpProtocol.UDP)) { + if (policy.srcPort() != 0) { + tsb.matchUdpSrc(TpPort.tpPort(policy.srcPort())); + } + if (policy.dstPort() != 0) { + tsb.matchUdpDst(TpPort.tpPort(policy.dstPort())); + } + } + } + + return tsb.build(); + } + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java new file mode 100644 index 00000000..59fc4ca7 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/RoutingRulePopulator.java @@ -0,0 +1,430 @@ +/* + * Copyright 2015 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.segmentrouting; + +import org.onlab.packet.Ethernet; +import org.onlab.packet.Ip4Address; +import org.onlab.packet.Ip4Prefix; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MacAddress; +import org.onlab.packet.MplsLabel; +import org.onlab.packet.VlanId; +import org.onosproject.segmentrouting.grouphandler.NeighborSet; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criteria; +import org.onosproject.net.flowobjective.DefaultFilteringObjective; +import org.onosproject.net.flowobjective.DefaultForwardingObjective; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.Objective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.flowobjective.ForwardingObjective.Builder; +import org.onosproject.net.flowobjective.ObjectiveContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class RoutingRulePopulator { + + private static final Logger log = LoggerFactory + .getLogger(RoutingRulePopulator.class); + + private AtomicLong rulePopulationCounter; + private SegmentRoutingManager srManager; + private DeviceConfiguration config; + /** + * Creates a RoutingRulePopulator object. + * + * @param srManager segment routing manager reference + */ + public RoutingRulePopulator(SegmentRoutingManager srManager) { + this.srManager = srManager; + this.config = checkNotNull(srManager.deviceConfiguration); + this.rulePopulationCounter = new AtomicLong(0); + } + + /** + * Resets the population counter. + */ + public void resetCounter() { + rulePopulationCounter.set(0); + } + + /** + * Returns the number of rules populated. + * + * @return number of rules + */ + public long getCounter() { + return rulePopulationCounter.get(); + } + + /** + * Populates IP flow rules for specific hosts directly connected to the + * switch. + * + * @param deviceId switch ID to set the rules + * @param hostIp host IP address + * @param hostMac host MAC address + * @param outPort port where the host is connected + */ + public void populateIpRuleForHost(DeviceId deviceId, Ip4Address hostIp, + MacAddress hostMac, PortNumber outPort) { + TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder(); + + sbuilder.matchIPDst(IpPrefix.valueOf(hostIp, 32)); + sbuilder.matchEthType(Ethernet.TYPE_IPV4); + + tbuilder.deferred() + .setEthDst(hostMac) + .setEthSrc(config.getDeviceMac(deviceId)) + .setOutput(outPort); + + TrafficTreatment treatment = tbuilder.build(); + TrafficSelector selector = sbuilder.build(); + + ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective + .builder().fromApp(srManager.appId).makePermanent() + .withSelector(selector).withTreatment(treatment) + .withPriority(100).withFlag(ForwardingObjective.Flag.SPECIFIC); + + log.debug("Installing IPv4 forwarding objective " + + "for host {} in switch {}", hostIp, deviceId); + srManager.flowObjectiveService. + forward(deviceId, + fwdBuilder. + add(new SRObjectiveContext(deviceId, + SRObjectiveContext.ObjectiveType.FORWARDING))); + rulePopulationCounter.incrementAndGet(); + } + + /** + * Populates IP flow rules for the subnets of the destination router. + * + * @param deviceId switch ID to set the rules + * @param subnets subnet information + * @param destSw destination switch ID + * @param nextHops next hop switch ID list + * @return true if all rules are set successfully, false otherwise + */ + public boolean populateIpRuleForSubnet(DeviceId deviceId, + List<Ip4Prefix> subnets, + DeviceId destSw, + Set<DeviceId> nextHops) { + + for (IpPrefix subnet : subnets) { + if (!populateIpRuleForRouter(deviceId, subnet, destSw, nextHops)) { + return false; + } + } + + return true; + } + + /** + * Populates IP flow rules for the router IP address. + * + * @param deviceId device ID to set the rules + * @param ipPrefix the IP address of the destination router + * @param destSw device ID of the destination router + * @param nextHops next hop switch ID list + * @return true if all rules are set successfully, false otherwise + */ + public boolean populateIpRuleForRouter(DeviceId deviceId, + IpPrefix ipPrefix, DeviceId destSw, + Set<DeviceId> nextHops) { + + TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder(); + + sbuilder.matchIPDst(ipPrefix); + sbuilder.matchEthType(Ethernet.TYPE_IPV4); + + NeighborSet ns = null; + + // If the next hop is the same as the final destination, then MPLS label + // is not set. + if (nextHops.size() == 1 && nextHops.toArray()[0].equals(destSw)) { + tbuilder.deferred().decNwTtl(); + ns = new NeighborSet(nextHops); + } else { + tbuilder.deferred().copyTtlOut(); + ns = new NeighborSet(nextHops, config.getSegmentId(destSw)); + } + + TrafficTreatment treatment = tbuilder.build(); + TrafficSelector selector = sbuilder.build(); + + if (srManager.getNextObjectiveId(deviceId, ns) <= 0) { + log.warn("No next objective in {} for ns: {}", deviceId, ns); + return false; + } + + ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective + .builder() + .fromApp(srManager.appId) + .makePermanent() + .nextStep(srManager.getNextObjectiveId(deviceId, ns)) + .withTreatment(treatment) + .withSelector(selector) + .withPriority(100) + .withFlag(ForwardingObjective.Flag.SPECIFIC); + log.debug("Installing IPv4 forwarding objective " + + "for router IP/subnet {} in switch {}", + ipPrefix, + deviceId); + srManager.flowObjectiveService. + forward(deviceId, + fwdBuilder. + add(new SRObjectiveContext(deviceId, + SRObjectiveContext.ObjectiveType.FORWARDING))); + rulePopulationCounter.incrementAndGet(); + + return true; + } + + /** + * Populates MPLS flow rules to all transit routers. + * + * @param deviceId device ID of the switch to set the rules + * @param destSwId destination switch device ID + * @param nextHops next hops switch ID list + * @return true if all rules are set successfully, false otherwise + */ + public boolean populateMplsRule(DeviceId deviceId, DeviceId destSwId, + Set<DeviceId> nextHops) { + + TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder(); + List<ForwardingObjective.Builder> fwdObjBuilders = new ArrayList<ForwardingObjective.Builder>(); + + // TODO Handle the case of Bos == false + sbuilder.matchMplsLabel(MplsLabel.mplsLabel(config.getSegmentId(destSwId))); + sbuilder.matchEthType(Ethernet.MPLS_UNICAST); + + // If the next hop is the destination router, do PHP + if (nextHops.size() == 1 && destSwId.equals(nextHops.toArray()[0])) { + log.debug("populateMplsRule: Installing MPLS forwarding objective for " + + "label {} in switch {} with PHP", + config.getSegmentId(destSwId), + deviceId); + + ForwardingObjective.Builder fwdObjBosBuilder = + getMplsForwardingObjective(deviceId, + destSwId, + nextHops, + true, + true); + // TODO: Check with Sangho on why we need this + ForwardingObjective.Builder fwdObjNoBosBuilder = + getMplsForwardingObjective(deviceId, + destSwId, + nextHops, + true, + false); + if (fwdObjBosBuilder != null) { + fwdObjBuilders.add(fwdObjBosBuilder); + } else { + log.warn("Failed to set MPLS rules."); + return false; + } + } else { + log.debug("Installing MPLS forwarding objective for " + + "label {} in switch {} without PHP", + config.getSegmentId(destSwId), + deviceId); + + ForwardingObjective.Builder fwdObjBosBuilder = + getMplsForwardingObjective(deviceId, + destSwId, + nextHops, + false, + true); + // TODO: Check with Sangho on why we need this + ForwardingObjective.Builder fwdObjNoBosBuilder = + getMplsForwardingObjective(deviceId, + destSwId, + nextHops, + false, + false); + if (fwdObjBosBuilder != null) { + fwdObjBuilders.add(fwdObjBosBuilder); + } else { + log.warn("Failed to set MPLS rules."); + return false; + } + } + + TrafficSelector selector = sbuilder.build(); + for (ForwardingObjective.Builder fwdObjBuilder : fwdObjBuilders) { + ((Builder) ((Builder) fwdObjBuilder.fromApp(srManager.appId) + .makePermanent()).withSelector(selector) + .withPriority(100)) + .withFlag(ForwardingObjective.Flag.SPECIFIC); + srManager.flowObjectiveService. + forward(deviceId, + fwdObjBuilder. + add(new SRObjectiveContext(deviceId, + SRObjectiveContext.ObjectiveType.FORWARDING))); + rulePopulationCounter.incrementAndGet(); + } + + return true; + } + + private ForwardingObjective.Builder getMplsForwardingObjective(DeviceId deviceId, + DeviceId destSw, + Set<DeviceId> nextHops, + boolean phpRequired, + boolean isBos) { + + ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective + .builder().withFlag(ForwardingObjective.Flag.SPECIFIC); + + TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder(); + + if (phpRequired) { + log.debug("getMplsForwardingObjective: php required"); + tbuilder.deferred().copyTtlIn(); + if (isBos) { + tbuilder.deferred().popMpls(Ethernet.TYPE_IPV4).decNwTtl(); + } else { + tbuilder.deferred().popMpls(Ethernet.MPLS_UNICAST).decMplsTtl(); + } + } else { + log.debug("getMplsForwardingObjective: php not required"); + tbuilder.deferred().decMplsTtl(); + } + + if (!isECMPSupportedInTransitRouter() && !config.isEdgeDevice(deviceId)) { + PortNumber port = selectOnePort(deviceId, nextHops); + DeviceId nextHop = (DeviceId) nextHops.toArray()[0]; + if (port == null) { + log.warn("No link from {} to {}", deviceId, nextHops); + return null; + } + tbuilder.deferred() + .setEthSrc(config.getDeviceMac(deviceId)) + .setEthDst(config.getDeviceMac(nextHop)) + .setOutput(port); + fwdBuilder.withTreatment(tbuilder.build()); + } else { + NeighborSet ns = new NeighborSet(nextHops); + fwdBuilder.withTreatment(tbuilder.build()); + fwdBuilder.nextStep(srManager + .getNextObjectiveId(deviceId, ns)); + } + + return fwdBuilder; + } + + private boolean isECMPSupportedInTransitRouter() { + + // TODO: remove this function when objectives subsystem is supported. + return false; + } + + /** + * Populates VLAN flows rules. All packets are forwarded to TMAC table. + * + * @param deviceId switch ID to set the rules + */ + public void populateTableVlan(DeviceId deviceId) { + FilteringObjective.Builder fob = DefaultFilteringObjective.builder(); + fob.withKey(Criteria.matchInPort(PortNumber.ALL)) + .addCondition(Criteria.matchVlanId(VlanId.NONE)); + fob.permit().fromApp(srManager.appId); + log.debug("populateTableVlan: Installing filtering objective for untagged packets"); + srManager.flowObjectiveService. + filter(deviceId, + fob.add(new SRObjectiveContext(deviceId, + SRObjectiveContext.ObjectiveType.FILTER))); + } + + /** + * Populates TMAC table rules. IP packets are forwarded to IP table. MPLS + * packets are forwarded to MPLS table. + * + * @param deviceId switch ID to set the rules + */ + public void populateTableTMac(DeviceId deviceId) { + + FilteringObjective.Builder fob = DefaultFilteringObjective.builder(); + fob.withKey(Criteria.matchInPort(PortNumber.ALL)) + .addCondition(Criteria.matchEthDst(config + .getDeviceMac(deviceId))); + fob.permit().fromApp(srManager.appId); + log.debug("populateTableVlan: Installing filtering objective for router mac"); + srManager.flowObjectiveService. + filter(deviceId, + fob.add(new SRObjectiveContext(deviceId, + SRObjectiveContext.ObjectiveType.FILTER))); + } + + private PortNumber selectOnePort(DeviceId srcId, Set<DeviceId> destIds) { + + Set<Link> links = srManager.linkService.getDeviceLinks(srcId); + for (DeviceId destId: destIds) { + for (Link link : links) { + if (link.dst().deviceId().equals(destId)) { + return link.src().port(); + } else if (link.src().deviceId().equals(destId)) { + return link.dst().port(); + } + } + } + + return null; + } + + private static class SRObjectiveContext implements ObjectiveContext { + enum ObjectiveType { + FILTER, + FORWARDING + } + final DeviceId deviceId; + final ObjectiveType type; + + SRObjectiveContext(DeviceId deviceId, ObjectiveType type) { + this.deviceId = deviceId; + this.type = type; + } + @Override + public void onSuccess(Objective objective) { + log.debug("{} objective operation successful in device {}", + type.name(), deviceId); + } + + @Override + public void onError(Objective objective, ObjectiveError error) { + log.warn("{} objective {} operation failed with error: {} in device {}", + type.name(), objective, error, deviceId); + } + } + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java new file mode 100644 index 00000000..874faabf --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingManager.java @@ -0,0 +1,517 @@ +/* + * Copyright 2015 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.segmentrouting; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.packet.Ethernet; +import org.onlab.packet.IPv4; +import org.onlab.util.KryoNamespace; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.event.Event; +import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler; +import org.onosproject.segmentrouting.grouphandler.NeighborSet; +import org.onosproject.segmentrouting.grouphandler.NeighborSetNextObjectiveStoreKey; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.Port; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.flowobjective.FlowObjectiveService; +import org.onosproject.net.group.GroupKey; +import org.onosproject.net.host.HostService; +import org.onosproject.net.intent.IntentService; +import org.onosproject.net.link.LinkEvent; +import org.onosproject.net.link.LinkListener; +import org.onosproject.net.link.LinkService; +import org.onosproject.net.packet.InboundPacket; +import org.onosproject.net.packet.PacketContext; +import org.onosproject.net.packet.PacketProcessor; +import org.onosproject.net.packet.PacketService; +import org.onosproject.net.topology.TopologyService; +import org.onosproject.segmentrouting.config.NetworkConfigManager; +import org.onosproject.store.service.EventuallyConsistentMap; +import org.onosproject.store.service.EventuallyConsistentMapBuilder; +import org.onosproject.store.service.StorageService; +import org.onosproject.store.service.WallClockTimestamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URI; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@SuppressWarnings("ALL") +@Service +@Component(immediate = true) +public class SegmentRoutingManager implements SegmentRoutingService { + + private static Logger log = LoggerFactory + .getLogger(SegmentRoutingManager.class); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected TopologyService topologyService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PacketService packetService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentService intentService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected HostService hostService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowObjectiveService flowObjectiveService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkService linkService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipService mastershipService; + + protected ArpHandler arpHandler = null; + protected IcmpHandler icmpHandler = null; + protected IpHandler ipHandler = null; + protected RoutingRulePopulator routingRulePopulator = null; + protected ApplicationId appId; + protected DeviceConfiguration deviceConfiguration = null; + + + private DefaultRoutingHandler defaultRoutingHandler = null; + private TunnelHandler tunnelHandler = null; + private PolicyHandler policyHandler = null; + private InternalPacketProcessor processor = new InternalPacketProcessor(); + private InternalEventHandler eventHandler = new InternalEventHandler(); + + private ScheduledExecutorService executorService = Executors + .newScheduledThreadPool(1); + + private static ScheduledFuture<?> eventHandlerFuture = null; + private ConcurrentLinkedQueue<Event> eventQueue = new ConcurrentLinkedQueue<Event>(); + private Map<DeviceId, DefaultGroupHandler> groupHandlerMap = new ConcurrentHashMap<DeviceId, DefaultGroupHandler>(); + // Per device next objective ID store with (device id + neighbor set) as key + private EventuallyConsistentMap<NeighborSetNextObjectiveStoreKey, + Integer> nsNextObjStore = null; + private EventuallyConsistentMap<String, Tunnel> tunnelStore = null; + private EventuallyConsistentMap<String, Policy> policyStore = null; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected StorageService storageService; + + private NetworkConfigManager networkConfigService = new NetworkConfigManager();; + + private Object threadSchedulerLock = new Object(); + private static int numOfEventsQueued = 0; + private static int numOfEventsExecuted = 0; + private static int numOfHandlerExecution = 0; + private static int numOfHandlerScheduled = 0; + + private KryoNamespace.Builder kryoBuilder = null; + + @Activate + protected void activate() { + appId = coreService + .registerApplication("org.onosproject.segmentrouting"); + + kryoBuilder = new KryoNamespace.Builder() + .register(NeighborSetNextObjectiveStoreKey.class, + NeighborSet.class, + DeviceId.class, + URI.class, + WallClockTimestamp.class, + org.onosproject.cluster.NodeId.class, + HashSet.class, + Tunnel.class, + DefaultTunnel.class, + Policy.class, + TunnelPolicy.class, + Policy.Type.class + ); + + log.debug("Creating EC map nsnextobjectivestore"); + EventuallyConsistentMapBuilder<NeighborSetNextObjectiveStoreKey, Integer> + nsNextObjMapBuilder = storageService.eventuallyConsistentMapBuilder(); + + nsNextObjStore = nsNextObjMapBuilder + .withName("nsnextobjectivestore") + .withSerializer(kryoBuilder) + .withTimestampProvider((k, v) -> new WallClockTimestamp()) + .build(); + log.trace("Current size {}", nsNextObjStore.size()); + + EventuallyConsistentMapBuilder<String, Tunnel> tunnelMapBuilder = + storageService.eventuallyConsistentMapBuilder(); + + tunnelStore = tunnelMapBuilder + .withName("tunnelstore") + .withSerializer(kryoBuilder) + .withTimestampProvider((k, v) -> new WallClockTimestamp()) + .build(); + + EventuallyConsistentMapBuilder<String, Policy> policyMapBuilder = + storageService.eventuallyConsistentMapBuilder(); + + policyStore = policyMapBuilder + .withName("policystore") + .withSerializer(kryoBuilder) + .withTimestampProvider((k, v) -> new WallClockTimestamp()) + .build(); + + networkConfigService.init(); + deviceConfiguration = new DeviceConfiguration(networkConfigService); + arpHandler = new ArpHandler(this); + icmpHandler = new IcmpHandler(this); + ipHandler = new IpHandler(this); + routingRulePopulator = new RoutingRulePopulator(this); + defaultRoutingHandler = new DefaultRoutingHandler(this); + tunnelHandler = new TunnelHandler(linkService, deviceConfiguration, + groupHandlerMap, tunnelStore); + policyHandler = new PolicyHandler(appId, deviceConfiguration, + flowObjectiveService, tunnelHandler, policyStore); + + packetService.addProcessor(processor, PacketProcessor.director(2)); + linkService.addListener(new InternalLinkListener()); + deviceService.addListener(new InternalDeviceListener()); + + for (Device device : deviceService.getDevices()) { + //Irrespective whether the local is a MASTER or not for this device, + //create group handler instance and push default TTP flow rules. + //Because in a multi-instance setup, instances can initiate + //groups for any devices. Also the default TTP rules are needed + //to be pushed before inserting any IP table entries for any device + DefaultGroupHandler groupHandler = DefaultGroupHandler + .createGroupHandler(device.id(), appId, + deviceConfiguration, linkService, + flowObjectiveService, + nsNextObjStore); + groupHandlerMap.put(device.id(), groupHandler); + defaultRoutingHandler.populateTtpRules(device.id()); + } + + defaultRoutingHandler.startPopulationProcess(); + log.info("Started"); + + } + + @Deactivate + protected void deactivate() { + packetService.removeProcessor(processor); + processor = null; + log.info("Stopped"); + } + + + @Override + public List<Tunnel> getTunnels() { + return tunnelHandler.getTunnels(); + } + + @Override + public TunnelHandler.Result createTunnel(Tunnel tunnel) { + return tunnelHandler.createTunnel(tunnel); + } + + @Override + public TunnelHandler.Result removeTunnel(Tunnel tunnel) { + for (Policy policy: policyHandler.getPolicies()) { + if (policy.type() == Policy.Type.TUNNEL_FLOW) { + TunnelPolicy tunnelPolicy = (TunnelPolicy) policy; + if (tunnelPolicy.tunnelId().equals(tunnel.id())) { + log.warn("Cannot remove the tunnel used by a policy"); + return TunnelHandler.Result.TUNNEL_IN_USE; + } + } + } + return tunnelHandler.removeTunnel(tunnel); + } + + @Override + public PolicyHandler.Result removePolicy(Policy policy) { + return policyHandler.removePolicy(policy); + } + + @Override + public PolicyHandler.Result createPolicy(Policy policy) { + return policyHandler.createPolicy(policy); + } + + @Override + public List<Policy> getPolicies() { + return policyHandler.getPolicies(); + } + + /** + * Returns the tunnel object with the tunnel ID. + * + * @param tunnelId Tunnel ID + * @return Tunnel reference + */ + public Tunnel getTunnel(String tunnelId) { + return tunnelHandler.getTunnel(tunnelId); + } + + /** + * Returns the GrouopKey object for the device and the NighborSet given. + * + * @param ns NeightborSet object for the GroupKey + * @return GroupKey object for the NeighborSet + */ + public GroupKey getGroupKey(NeighborSet ns) { + + for (DefaultGroupHandler groupHandler : groupHandlerMap.values()) { + return groupHandler.getGroupKey(ns); + } + + return null; + } + + /** + * Returns the next objective ID for the NeighborSet given. If the nextObjectiveID does not exist, + * a new one is created and returned. + * + * @param deviceId Device ID + * @param ns NegighborSet + * @return next objective ID + */ + public int getNextObjectiveId(DeviceId deviceId, NeighborSet ns) { + + if (groupHandlerMap.get(deviceId) != null) { + log.trace("getNextObjectiveId query in device {}", deviceId); + return groupHandlerMap + .get(deviceId).getNextObjectiveId(ns); + } else { + log.warn("getNextObjectiveId query in device {} not found", deviceId); + return -1; + } + } + + private class InternalPacketProcessor implements PacketProcessor { + + @Override + public void process(PacketContext context) { + + if (context.isHandled()) { + return; + } + + InboundPacket pkt = context.inPacket(); + Ethernet ethernet = pkt.parsed(); + + if (ethernet.getEtherType() == Ethernet.TYPE_ARP) { + arpHandler.processPacketIn(pkt); + } else if (ethernet.getEtherType() == Ethernet.TYPE_IPV4) { + IPv4 ipPacket = (IPv4) ethernet.getPayload(); + ipHandler.addToPacketBuffer(ipPacket); + if (ipPacket.getProtocol() == IPv4.PROTOCOL_ICMP) { + icmpHandler.processPacketIn(pkt); + } else { + ipHandler.processPacketIn(pkt); + } + } + } + } + + private class InternalLinkListener implements LinkListener { + @Override + public void event(LinkEvent event) { + if (event.type() == LinkEvent.Type.LINK_ADDED + || event.type() == LinkEvent.Type.LINK_REMOVED) { + log.debug("Event {} received from Link Service", event.type()); + scheduleEventHandlerIfNotScheduled(event); + } + } + } + + private class InternalDeviceListener implements DeviceListener { + + @Override + public void event(DeviceEvent event) { + /*if (mastershipService.getLocalRole(event.subject().id()) != MastershipRole.MASTER) { + log.debug("Local role {} is not MASTER for device {}", + mastershipService.getLocalRole(event.subject().id()), + event.subject().id()); + return; + }*/ + + switch (event.type()) { + case DEVICE_ADDED: + case PORT_REMOVED: + case DEVICE_UPDATED: + case DEVICE_AVAILABILITY_CHANGED: + log.debug("Event {} received from Device Service", event.type()); + scheduleEventHandlerIfNotScheduled(event); + break; + default: + } + } + } + + private void scheduleEventHandlerIfNotScheduled(Event event) { + + synchronized (threadSchedulerLock) { + eventQueue.add(event); + numOfEventsQueued++; + + if ((numOfHandlerScheduled - numOfHandlerExecution) == 0) { + //No pending scheduled event handling threads. So start a new one. + eventHandlerFuture = executorService + .schedule(eventHandler, 100, TimeUnit.MILLISECONDS); + numOfHandlerScheduled++; + } + log.trace("numOfEventsQueued {}, numOfEventHanlderScheduled {}", + numOfEventsQueued, + numOfHandlerScheduled); + } + } + + private class InternalEventHandler implements Runnable { + + @Override + public void run() { + try { + while (true) { + Event event = null; + synchronized (threadSchedulerLock) { + if (!eventQueue.isEmpty()) { + event = eventQueue.poll(); + numOfEventsExecuted++; + } else { + numOfHandlerExecution++; + log.debug("numOfHandlerExecution {} numOfEventsExecuted {}", + numOfHandlerExecution, numOfEventsExecuted); + break; + } + } + if (event.type() == LinkEvent.Type.LINK_ADDED) { + processLinkAdded((Link) event.subject()); + } else if (event.type() == LinkEvent.Type.LINK_REMOVED) { + processLinkRemoved((Link) event.subject()); + //} else if (event.type() == GroupEvent.Type.GROUP_ADDED) { + // processGroupAdded((Group) event.subject()); + } else if (event.type() == DeviceEvent.Type.DEVICE_ADDED || + event.type() == DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED || + event.type() == DeviceEvent.Type.DEVICE_UPDATED) { + if (deviceService.isAvailable(((Device) event.subject()).id())) { + processDeviceAdded((Device) event.subject()); + } + } else if (event.type() == DeviceEvent.Type.PORT_REMOVED) { + processPortRemoved((Device) event.subject(), + ((DeviceEvent) event).port()); + } else { + log.warn("Unhandled event type: {}", event.type()); + } + } + } catch (Exception e) { + log.error("SegmentRouting event handler " + + "thread thrown an exception: {}", e); + } + } + } + + private void processLinkAdded(Link link) { + log.debug("A new link {} was added", link.toString()); + + //Irrespective whether the local is a MASTER or not for this device, + //create group handler instance and push default TTP flow rules. + //Because in a multi-instance setup, instances can initiate + //groups for any devices. Also the default TTP rules are needed + //to be pushed before inserting any IP table entries for any device + DefaultGroupHandler groupHandler = groupHandlerMap.get(link.src() + .deviceId()); + if (groupHandler != null) { + groupHandler.linkUp(link); + } else { + Device device = deviceService.getDevice(link.src().deviceId()); + if (device != null) { + log.warn("processLinkAdded: Link Added " + + "Notification without Device Added " + + "event, still handling it"); + processDeviceAdded(device); + groupHandler = groupHandlerMap.get(link.src() + .deviceId()); + groupHandler.linkUp(link); + } + } + + log.trace("Starting optimized route population process"); + defaultRoutingHandler.populateRoutingRulesForLinkStatusChange(null); + //log.trace("processLinkAdded: re-starting route population process"); + //defaultRoutingHandler.startPopulationProcess(); + } + + private void processLinkRemoved(Link link) { + log.debug("A link {} was removed", link.toString()); + DefaultGroupHandler groupHandler = groupHandlerMap.get(link.src().deviceId()); + if (groupHandler != null) { + groupHandler.portDown(link.src().port()); + } + log.trace("Starting optimized route population process"); + defaultRoutingHandler.populateRoutingRulesForLinkStatusChange(link); + //log.trace("processLinkRemoved: re-starting route population process"); + //defaultRoutingHandler.startPopulationProcess(); + } + + private void processDeviceAdded(Device device) { + log.debug("A new device with ID {} was added", device.id()); + //Irrespective whether the local is a MASTER or not for this device, + //create group handler instance and push default TTP flow rules. + //Because in a multi-instance setup, instances can initiate + //groups for any devices. Also the default TTP rules are needed + //to be pushed before inserting any IP table entries for any device + DefaultGroupHandler dgh = DefaultGroupHandler. + createGroupHandler(device.id(), + appId, + deviceConfiguration, + linkService, + flowObjectiveService, + nsNextObjStore); + groupHandlerMap.put(device.id(), dgh); + defaultRoutingHandler.populateTtpRules(device.id()); + } + + private void processPortRemoved(Device device, Port port) { + log.debug("Port {} was removed", port.toString()); + DefaultGroupHandler groupHandler = groupHandlerMap.get(device.id()); + if (groupHandler != null) { + groupHandler.portDown(port.number()); + } + } + + + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java new file mode 100644 index 00000000..44bd453c --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/SegmentRoutingService.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015 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.segmentrouting; + +import java.util.List; + +/** + * Segment Routing Service for REST API. + */ +public interface SegmentRoutingService { + + /** + * Returns all tunnels. + * + * @return list of tunnels + */ + List<Tunnel> getTunnels(); + + /** + * Creates a tunnel. + * + * @param tunnel tunnel reference to create + * @return WRONG_PATH if the tunnel path is wrong, ID_EXISTS if the tunnel ID + * exists already, TUNNEL_EXISTS if the same tunnel exists, INTERNAL_ERROR + * if the tunnel creation failed internally, SUCCESS if the tunnel is created + * successfully + */ + TunnelHandler.Result createTunnel(Tunnel tunnel); + + /** + * Returns all policies. + * + * @return list of policy + */ + List<Policy> getPolicies(); + + /** + * Creates a policy. + * + * @param policy policy reference to create + * @return ID_EXISTS if the same policy ID exists, + * POLICY_EXISTS if the same policy exists, TUNNEL_NOT_FOUND if the tunnel + * does not exists, UNSUPPORTED_TYPE if the policy type is not supported, + * SUCCESS if the policy is created successfully. + */ + PolicyHandler.Result createPolicy(Policy policy); + + /** + * Removes a tunnel. + * + * @param tunnel tunnel reference to remove + * @return TUNNEL_NOT_FOUND if the tunnel to remove does not exists, + * INTERNAL_ERROR if the tunnel creation failed internally, SUCCESS + * if the tunnel is created successfully. + */ + TunnelHandler.Result removeTunnel(Tunnel tunnel); + + /** + * Removes a policy. + * + * @param policy policy reference to remove + * @return POLICY_NOT_FOUND if the policy to remove does not exists, + * SUCCESS if it is removed successfully + */ + PolicyHandler.Result removePolicy(Policy policy); +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/Tunnel.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/Tunnel.java new file mode 100644 index 00000000..783d253e --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/Tunnel.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015 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.segmentrouting; + +import java.util.List; + +/** + * Tunnel interface. + */ +public interface Tunnel { + + /** + * Returns the tunnel ID. + * + * @return tunnel ID + */ + String id(); + + /** + * Returns Segment IDs for the tunnel including source and destination. + * + * @return List of Node ID + */ + List<Integer> labelIds(); + + /** + * Returns the group ID for the tunnel. + * + * @return group ID + */ + int groupId(); + + /** + * Sets group ID for the tunnel. + * + * @param groupId group identifier + */ + void setGroupId(int groupId); + + /** + * Sets the flag to allow to remove the group or not. + * + * @param ok the flag; true - allow to remove + */ + void allowToRemoveGroup(boolean ok); + + /** + * Checks if it is allowed to remove the group for the tunnel. + * + * @return true if allowed, false otherwise + */ + boolean isAllowedToRemoveGroup(); +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/TunnelHandler.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/TunnelHandler.java new file mode 100644 index 00000000..820bb40a --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/TunnelHandler.java @@ -0,0 +1,199 @@ +/* + * Copyright 2015 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.segmentrouting; + +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.link.LinkService; +import org.onosproject.segmentrouting.grouphandler.DefaultGroupHandler; +import org.onosproject.segmentrouting.grouphandler.NeighborSet; +import org.onosproject.store.service.EventuallyConsistentMap; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Tunnel Handler. + */ +public class TunnelHandler { + protected final Logger log = getLogger(getClass()); + + private final DeviceConfiguration config; + private final EventuallyConsistentMap<String, Tunnel> tunnelStore; + private Map<DeviceId, DefaultGroupHandler> groupHandlerMap; + private LinkService linkService; + + public enum Result { + SUCCESS, + WRONG_PATH, + TUNNEL_EXISTS, + ID_EXISTS, + TUNNEL_NOT_FOUND, + TUNNEL_IN_USE, + INTERNAL_ERROR + } + + public TunnelHandler(LinkService linkService, + DeviceConfiguration deviceConfiguration, + Map<DeviceId, DefaultGroupHandler> groupHandlerMap, + EventuallyConsistentMap<String, Tunnel> tunnelStore) { + this.linkService = linkService; + this.config = deviceConfiguration; + this.groupHandlerMap = groupHandlerMap; + this.tunnelStore = tunnelStore; + } + + /** + * Creates a tunnel. + * + * @param tunnel tunnel reference to create a tunnel + * @return WRONG_PATH if the tunnel path is wrong, ID_EXISTS if the tunnel ID + * exists already, TUNNEL_EXISTS if the same tunnel exists, INTERNAL_ERROR + * if the tunnel creation failed internally, SUCCESS if the tunnel is created + * successfully + */ + public Result createTunnel(Tunnel tunnel) { + + if (tunnel.labelIds().isEmpty() || tunnel.labelIds().size() < 3) { + log.error("More than one router needs to specified to created a tunnel"); + return Result.WRONG_PATH; + } + + if (tunnelStore.containsKey(tunnel.id())) { + log.warn("The same tunnel ID exists already"); + return Result.ID_EXISTS; + } + + if (tunnelStore.containsValue(tunnel)) { + log.warn("The same tunnel exists already"); + return Result.TUNNEL_EXISTS; + } + + int groupId = createGroupsForTunnel(tunnel); + if (groupId < 0) { + log.error("Failed to create groups for the tunnel"); + return Result.INTERNAL_ERROR; + } + + tunnel.setGroupId(groupId); + tunnelStore.put(tunnel.id(), tunnel); + + return Result.SUCCESS; + } + + /** + * Removes the tunnel with the tunnel ID given. + * + * @param tunnelInfo tunnel information to delete tunnels + * @return TUNNEL_NOT_FOUND if the tunnel to remove does not exists, + * INTERNAL_ERROR if the tunnel creation failed internally, SUCCESS + * if the tunnel is created successfully. + */ + public Result removeTunnel(Tunnel tunnelInfo) { + + Tunnel tunnel = tunnelStore.get(tunnelInfo.id()); + if (tunnel != null) { + DeviceId deviceId = config.getDeviceId(tunnel.labelIds().get(0)); + if (tunnel.isAllowedToRemoveGroup()) { + if (groupHandlerMap.get(deviceId).removeGroup(tunnel.groupId())) { + tunnelStore.remove(tunnel.id()); + } else { + log.error("Failed to remove the tunnel {}", tunnelInfo.id()); + return Result.INTERNAL_ERROR; + } + } else { + log.debug("The group is not removed because it is being used."); + tunnelStore.remove(tunnel.id()); + } + } else { + log.error("No tunnel found for tunnel ID {}", tunnelInfo.id()); + return Result.TUNNEL_NOT_FOUND; + } + + return Result.SUCCESS; + } + + /** + * Returns the tunnel with the tunnel ID given. + * + * @param tid Tunnel ID + * @return Tunnel reference + */ + public Tunnel getTunnel(String tid) { + return tunnelStore.get(tid); + } + + /** + * Returns all tunnels. + * + * @return list of Tunnels + */ + public List<Tunnel> getTunnels() { + List<Tunnel> tunnels = new ArrayList<>(); + tunnelStore.values().forEach(tunnel -> tunnels.add( + new DefaultTunnel((DefaultTunnel) tunnel))); + + return tunnels; + } + + private int createGroupsForTunnel(Tunnel tunnel) { + + List<Integer> portNumbers; + final int groupError = -1; + + DeviceId deviceId = config.getDeviceId(tunnel.labelIds().get(0)); + if (deviceId == null) { + log.warn("No device found for SID {}", tunnel.labelIds().get(0)); + return groupError; + } else if (groupHandlerMap.get(deviceId) == null) { + log.warn("group handler not found for {}", deviceId); + return groupError; + } + Set<DeviceId> deviceIds = new HashSet<>(); + int sid = tunnel.labelIds().get(1); + if (config.isAdjacencySid(deviceId, sid)) { + portNumbers = config.getPortsForAdjacencySid(deviceId, sid); + for (Link link: linkService.getDeviceEgressLinks(deviceId)) { + for (Integer port: portNumbers) { + if (link.src().port().toLong() == port) { + deviceIds.add(link.dst().deviceId()); + } + } + } + } else { + deviceIds.add(config.getDeviceId(sid)); + } + + NeighborSet ns = new NeighborSet(deviceIds, tunnel.labelIds().get(2)); + + // If the tunnel reuses any existing groups, then tunnel handler + // should not remove the group. + if (groupHandlerMap.get(deviceId).hasNextObjectiveId(ns)) { + tunnel.allowToRemoveGroup(false); + } else { + tunnel.allowToRemoveGroup(true); + } + + return groupHandlerMap.get(deviceId).getNextObjectiveId(ns); + } + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/TunnelPolicy.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/TunnelPolicy.java new file mode 100644 index 00000000..06dbdb21 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/TunnelPolicy.java @@ -0,0 +1,291 @@ +/* + * Copyright 2015 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.segmentrouting; + +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Tunnel Policy. + */ +public final class TunnelPolicy implements Policy { + + private final Type type; + private final String id; + private final int priority; + private final String tunnelId; + private String dstIp; + private String srcIp; + private String ipProto; + private short srcPort; + private short dstPort; + + private TunnelPolicy(String policyId, Type type, int priority, String tunnelId, String srcIp, + String dstIp, String ipProto, short srcPort, short dstPort) { + this.id = checkNotNull(policyId); + this.type = type; + this.tunnelId = tunnelId; + this.priority = priority; + this.dstIp = dstIp; + this.srcIp = srcIp; + this.ipProto = ipProto; + this.srcPort = srcPort; + this.dstPort = dstPort; + + } + + /** + * Creates a TunnelPolicy reference. + * + * @param p TunnelPolicy reference + */ + public TunnelPolicy(TunnelPolicy p) { + this.id = p.id; + this.type = p.type; + this.tunnelId = p.tunnelId; + this.priority = p.priority; + this.srcIp = p.srcIp; + this.dstIp = p.dstIp; + this.ipProto = p.ipProto; + this.srcPort = p.srcPort; + this.dstPort = p.dstPort; + } + + /** + * Returns the TunnelPolicy builder reference. + * + * @return TunnelPolicy builder + */ + public static TunnelPolicy.Builder builder() { + return new Builder(); + } + + @Override + public String id() { + return this.id; + } + + @Override + public int priority() { + return priority; + } + + @Override + public Type type() { + return type; + } + + @Override + public String srcIp() { + return srcIp; + } + + @Override + public String dstIp() { + return dstIp; + } + + @Override + public String ipProto() { + return ipProto; + } + + @Override + public short srcPort() { + return srcPort; + } + + @Override + public short dstPort() { + return dstPort; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o instanceof TunnelPolicy) { + TunnelPolicy that = (TunnelPolicy) o; + // We do not compare the policy ID + if (this.type.equals(that.type) && + this.tunnelId.equals(that.tunnelId) && + this.priority == that.priority && + this.srcIp.equals(that.srcIp) && + this.dstIp.equals(that.dstIp) && + this.srcPort == that.srcPort && + this.dstPort == that.dstPort && + this.ipProto.equals(that.ipProto)) { + return true; + } + } + + return false; + } + + @Override + public int hashCode() { + return Objects.hash(type, tunnelId, srcIp, dstIp, ipProto, + srcPort, dstPort, priority); + } + + /** + * Returns the tunnel ID of the policy. + * + * @return Tunnel ID + */ + public String tunnelId() { + return this.tunnelId; + } + + + /** + * Tunnel Policy Builder. + */ + public static final class Builder { + + private String id; + private Type type; + private int priority; + private String tunnelId; + private String dstIp; + private String srcIp; + private String ipProto; + private short srcPort; + private short dstPort; + + /** + * Sets the policy Id. + * + * @param id policy Id + * @return Builder object + */ + public Builder setPolicyId(String id) { + this.id = id; + + return this; + } + + /** + * Sets the policy type. + * + * @param type policy type + * @return Builder object + */ + public Builder setType(Type type) { + this.type = type; + + return this; + } + + /** + * Sets the source IP address. + * + * @param srcIp source IP address + * @return Builder object + */ + public Builder setSrcIp(String srcIp) { + this.srcIp = srcIp; + + return this; + } + + /** + * Sets the destination IP address. + * + * @param dstIp destination IP address + * @return Builder object + */ + public Builder setDstIp(String dstIp) { + this.dstIp = dstIp; + + return this; + } + + /** + * Sets the IP protocol. + * + * @param proto IP protocol + * @return Builder object + */ + public Builder setIpProto(String proto) { + this.ipProto = proto; + + return this; + } + + /** + * Sets the source port. + * + * @param srcPort source port + * @return Builder object + */ + public Builder setSrcPort(short srcPort) { + this.srcPort = srcPort; + + return this; + } + + /** + * Sets the destination port. + * + * @param dstPort destination port + * @return Builder object + */ + public Builder setDstPort(short dstPort) { + this.dstPort = dstPort; + + return this; + } + + /** + * Sets the priority of the policy. + * + * @param p priority + * @return Builder object + */ + public Builder setPriority(int p) { + this.priority = p; + + return this; + } + + /** + * Sets the tunnel Id. + * + * @param tunnelId tunnel Id + * @return Builder object + */ + public Builder setTunnelId(String tunnelId) { + this.tunnelId = tunnelId; + + return this; + } + + /** + * Builds the policy. + * + * @return Tunnel Policy reference + */ + public Policy build() { + return new TunnelPolicy(id, type, priority, tunnelId, srcIp, dstIp, + ipProto, srcPort, dstPort); + } + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/PolicyAddCommand.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/PolicyAddCommand.java new file mode 100644 index 00000000..b00633cd --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/PolicyAddCommand.java @@ -0,0 +1,132 @@ +/* + * Copyright 2015 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.segmentrouting.cli; + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.segmentrouting.Policy; +import org.onosproject.segmentrouting.PolicyHandler; +import org.onosproject.segmentrouting.SegmentRoutingService; +import org.onosproject.segmentrouting.TunnelPolicy; + +/** + * Command to add a new policy. + */ +@Command(scope = "onos", name = "srpolicy-add", + description = "Create a new policy") +public class PolicyAddCommand extends AbstractShellCommand { + + // TODO: Need to support skipping some parameters + + @Argument(index = 0, name = "ID", + description = "policy ID", + required = true, multiValued = false) + String policyId; + + @Argument(index = 1, name = "priority", + description = "priority", + required = true, multiValued = false) + int priority; + + @Argument(index = 2, name = "src_IP", + description = "src IP", + required = false, multiValued = false) + String srcIp; + + @Argument(index = 3, name = "src_port", + description = "src port", + required = false, multiValued = false) + short srcPort; + + @Argument(index = 4, name = "dst_IP", + description = "dst IP", + required = false, multiValued = false) + String dstIp; + + @Argument(index = 5, name = "dst_port", + description = "dst port", + required = false, multiValued = false) + short dstPort; + + @Argument(index = 6, name = "proto", + description = "IP protocol", + required = false, multiValued = false) + String proto; + + @Argument(index = 7, name = "policy_type", + description = "policy type", + required = true, multiValued = false) + String policyType; + + @Argument(index = 8, name = "tunnel_ID", + description = "tunnel ID", + required = false, multiValued = false) + String tunnelId; + + @Override + protected void execute() { + + SegmentRoutingService srService = + AbstractShellCommand.get(SegmentRoutingService.class); + + TunnelPolicy.Builder tpb = TunnelPolicy.builder().setPolicyId(policyId); + tpb.setPriority(priority); + tpb.setType(Policy.Type.valueOf(policyType)); + + if (srcIp != null) { + tpb.setSrcIp(srcIp); + } + if (dstIp != null) { + tpb.setDstIp(dstIp); + } + if (srcPort != 0) { + tpb.setSrcPort(srcPort); + } + if (dstPort != 0) { + tpb.setDstPort(dstPort); + } + if (!proto.equals("ip")) { + tpb.setIpProto(proto); + } + if (Policy.Type.valueOf(policyType) == Policy.Type.TUNNEL_FLOW) { + if (tunnelId == null) { + error("tunnel ID must be specified for TUNNEL_FLOW policy"); + return; + } + tpb.setTunnelId(tunnelId); + } + PolicyHandler.Result result = srService.createPolicy(tpb.build()); + + switch (result) { + case POLICY_EXISTS: + error("the same policy exists"); + break; + case ID_EXISTS: + error("the same policy ID exists"); + break; + case TUNNEL_NOT_FOUND: + error("the tunnel is not found"); + break; + case UNSUPPORTED_TYPE: + error("the policy type specified is not supported"); + break; + default: + break; + } + + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/PolicyListCommand.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/PolicyListCommand.java new file mode 100644 index 00000000..929c98c5 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/PolicyListCommand.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015 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.segmentrouting.cli; + +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.segmentrouting.Policy; +import org.onosproject.segmentrouting.SegmentRoutingService; +import org.onosproject.segmentrouting.TunnelPolicy; + +/** + * Command to show the list of policies. + */ +@Command(scope = "onos", name = "srpolicy-list", + description = "Lists all policies") +public class PolicyListCommand extends AbstractShellCommand { + + private static final String FORMAT_MAPPING_TUNNEL = + " id=%s, type=%s, prio=%d, src=%s, port=%d, dst=%s, port=%d, proto=%s, tunnel=%s"; + + @Override + protected void execute() { + + SegmentRoutingService srService = + AbstractShellCommand.get(SegmentRoutingService.class); + + srService.getPolicies().forEach(policy -> printPolicy(policy)); + } + + private void printPolicy(Policy policy) { + if (policy.type() == Policy.Type.TUNNEL_FLOW) { + print(FORMAT_MAPPING_TUNNEL, policy.id(), policy.type(), policy.priority(), + policy.srcIp(), policy.srcPort(), policy.dstIp(), policy.dstPort(), + (policy.ipProto() == null) ? "" : policy.ipProto(), + ((TunnelPolicy) policy).tunnelId()); + } + } +}
\ No newline at end of file diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/PolicyRemoveCommand.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/PolicyRemoveCommand.java new file mode 100644 index 00000000..34fe40d8 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/PolicyRemoveCommand.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015 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.segmentrouting.cli; + + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.segmentrouting.PolicyHandler; +import org.onosproject.segmentrouting.SegmentRoutingService; +import org.onosproject.segmentrouting.TunnelPolicy; + +/** + * Command to remove a policy. + */ +@Command(scope = "onos", name = "srpolicy-remove", + description = "Remove a policy") +public class PolicyRemoveCommand extends AbstractShellCommand { + + @Argument(index = 0, name = "policy ID", + description = "policy ID", + required = true, multiValued = false) + String policyId; + + @Override + protected void execute() { + + SegmentRoutingService srService = + AbstractShellCommand.get(SegmentRoutingService.class); + + TunnelPolicy.Builder tpb = TunnelPolicy.builder().setPolicyId(policyId); + PolicyHandler.Result result = srService.removePolicy(tpb.build()); + if (result == PolicyHandler.Result.POLICY_NOT_FOUND) { + print("ERROR: the policy is not found"); + } + } +}
\ No newline at end of file diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/TunnelAddCommand.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/TunnelAddCommand.java new file mode 100644 index 00000000..bb0ae549 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/TunnelAddCommand.java @@ -0,0 +1,80 @@ + +/* + * Copyright 2015 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.segmentrouting.cli; + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.segmentrouting.DefaultTunnel; +import org.onosproject.segmentrouting.SegmentRoutingService; +import org.onosproject.segmentrouting.Tunnel; +import org.onosproject.segmentrouting.TunnelHandler; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * Command to add a new tunnel. + */ +@Command(scope = "onos", name = "srtunnel-add", + description = "Create a new tunnel") +public class TunnelAddCommand extends AbstractShellCommand { + + @Argument(index = 0, name = "tunnel ID", + description = "tunnel ID", + required = true, multiValued = false) + String tunnelId; + + @Argument(index = 1, name = "label path", + description = "label path", + required = true, multiValued = false) + String labels; + + + @Override + protected void execute() { + + SegmentRoutingService srService = + AbstractShellCommand.get(SegmentRoutingService.class); + + List<Integer> labelIds = new ArrayList<>(); + StringTokenizer strToken = new StringTokenizer(labels, ","); + while (strToken.hasMoreTokens()) { + labelIds.add(Integer.valueOf(strToken.nextToken())); + } + Tunnel tunnel = new DefaultTunnel(tunnelId, labelIds); + + TunnelHandler.Result result = srService.createTunnel(tunnel); + switch (result) { + case ID_EXISTS: + print("ERROR: the same tunnel ID exists"); + break; + case TUNNEL_EXISTS: + print("ERROR: the same tunnel exists"); + break; + case INTERNAL_ERROR: + print("ERROR: internal tunnel creation error"); + break; + case WRONG_PATH: + print("ERROR: the tunnel path is wrong"); + break; + default: + break; + } + } +}
\ No newline at end of file diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/TunnelListCommand.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/TunnelListCommand.java new file mode 100644 index 00000000..fe510783 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/TunnelListCommand.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 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.segmentrouting.cli; + +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.segmentrouting.SegmentRoutingService; +import org.onosproject.segmentrouting.Tunnel; + +/** + * Command to show the list of tunnels. + */ +@Command(scope = "onos", name = "srtunnel-list", + description = "Lists all tunnels") +public class TunnelListCommand extends AbstractShellCommand { + + private static final String FORMAT_MAPPING = + " id=%s, path=%s"; + + @Override + protected void execute() { + + SegmentRoutingService srService = + AbstractShellCommand.get(SegmentRoutingService.class); + + srService.getTunnels().forEach(tunnel -> printTunnel(tunnel)); + } + + private void printTunnel(Tunnel tunnel) { + print(FORMAT_MAPPING, tunnel.id(), tunnel.labelIds()); + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/TunnelRemoveCommand.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/TunnelRemoveCommand.java new file mode 100644 index 00000000..cca22c30 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/TunnelRemoveCommand.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015 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.segmentrouting.cli; + + +import com.google.common.collect.Lists; +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.segmentrouting.DefaultTunnel; +import org.onosproject.segmentrouting.SegmentRoutingService; +import org.onosproject.segmentrouting.Tunnel; +import org.onosproject.segmentrouting.TunnelHandler; + +/** + * Command to remove a tunnel. + */ +@Command(scope = "onos", name = "srtunnel-remove", + description = "Remove a tunnel") +public class TunnelRemoveCommand extends AbstractShellCommand { + + @Argument(index = 0, name = "tunnel ID", + description = "tunnel ID", + required = true, multiValued = false) + String tunnelId; + + @Override + protected void execute() { + SegmentRoutingService srService = + AbstractShellCommand.get(SegmentRoutingService.class); + + Tunnel tunnel = new DefaultTunnel(tunnelId, Lists.newArrayList()); + TunnelHandler.Result result = srService.removeTunnel(tunnel); + switch (result) { + case TUNNEL_IN_USE: + print("ERROR: the tunnel is still in use"); + break; + case TUNNEL_NOT_FOUND: + print("ERROR: the tunnel is not found"); + break; + default: + break; + } + } +}
\ No newline at end of file diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/package-info.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/package-info.java new file mode 100644 index 00000000..1a9d3c78 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/cli/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Segment routing application CLI handlers. + */ +package org.onosproject.segmentrouting.cli;
\ No newline at end of file diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/NetworkConfig.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/NetworkConfig.java new file mode 100644 index 00000000..6ae0779e --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/NetworkConfig.java @@ -0,0 +1,336 @@ +package org.onosproject.segmentrouting.config; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.onosproject.net.DeviceId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Public class corresponding to JSON described data model. Defines the network + * configuration at startup. + */ +public class NetworkConfig { + protected static final Logger log = LoggerFactory.getLogger(NetworkConfig.class); + + @SuppressWarnings("unused") + private String comment; + + private Boolean restrictSwitches; + private Boolean restrictLinks; + private List<SwitchConfig> switches; + private List<LinkConfig> links; + + /** + * Default constructor. + */ + public NetworkConfig() { + switches = new ArrayList<SwitchConfig>(); + links = new ArrayList<LinkConfig>(); + } + + @JsonProperty("comment") + public void setComment(String c) { + log.trace("NetworkConfig: comment={}", c); + comment = c; + } + + @JsonProperty("restrictSwitches") + public void setRestrictSwitches(boolean rs) { + log.trace("NetworkConfig: restrictSwitches={}", rs); + restrictSwitches = rs; + } + + /** + * Returns default restrict configuration for switches. + * + * @return boolean + */ + public Boolean getRestrictSwitches() { + return restrictSwitches; + } + + @JsonProperty("restrictLinks") + public void setRestrictLinks(boolean rl) { + log.trace("NetworkConfig: restrictLinks={}", rl); + restrictLinks = rl; + } + + /** + * Returns default restrict configuration for links. + * + * @return boolean + */ + public Boolean getRestrictLinks() { + return restrictLinks; + } + + /** + * Returns configuration for switches. + * + * @return list of switch configuration + */ + public List<SwitchConfig> getSwitchConfig() { + return switches; + } + + @JsonProperty("switchConfig") + public void setSwitchConfig(List<SwitchConfig> switches2) { + log.trace("NetworkConfig: switchConfig={}", switches2); + this.switches = switches2; + } + + /** + * Java class corresponding to JSON described switch + * configuration data model. + */ + public static class SwitchConfig { + protected String nodeDpid; + protected String name; + protected String type; + protected boolean allowed; + protected double latitude; + protected double longitude; + protected Map<String, JsonNode> params; + protected Map<String, String> publishAttributes; + protected DeviceId dpid; + + /** + * Returns the configured "name" of a switch. + * + * @return string + */ + public String getName() { + return name; + } + + @JsonProperty("name") + public void setName(String name) { + log.trace("SwitchConfig: name={}", name); + this.name = name; + } + + /** + * Returns the data plane identifier of a switch. + * + * @return ONOS device identifier + */ + public DeviceId getDpid() { + return dpid; + } + + public void setDpid(DeviceId dpid) { + this.dpid = dpid; + this.nodeDpid = dpid.toString(); + } + + /** + * Returns the data plane identifier of a switch. + * + * @return string + */ + public String getNodeDpid() { + return nodeDpid; + } + + // mapper sets both DeviceId and string fields for dpid + @JsonProperty("nodeDpid") + public void setNodeDpid(String nodeDpid) { + log.trace("SwitchConfig: nodeDpid={}", nodeDpid); + this.nodeDpid = nodeDpid; + this.dpid = DeviceId.deviceId(nodeDpid); + } + + /** + * Returns the type of a switch. + * + * @return string + */ + public String getType() { + return type; + } + + @JsonProperty("type") + public void setType(String type) { + log.trace("SwitchConfig: type={}", type); + this.type = type; + } + + /** + * Returns the latitude of a switch. + * + * @return double + */ + public double getLatitude() { + return latitude; + } + + @JsonProperty("latitude") + public void setLatitude(double latitude) { + log.trace("SwitchConfig: latitude={}", latitude); + this.latitude = latitude; + } + + /** + * Returns the longitude of a switch. + * + * @return double + */ + public double getLongitude() { + return longitude; + } + + @JsonProperty("longitude") + public void setLongitude(double longitude) { + log.trace("SwitchConfig: longitude={}", longitude); + this.longitude = longitude; + } + + /** + * Returns the allowed flag for a switch. + * + * @return boolean + */ + public boolean isAllowed() { + return allowed; + } + + @JsonProperty("allowed") + public void setAllowed(boolean allowed) { + this.allowed = allowed; + } + + /** + * Returns the additional configured parameters of a switch. + * + * @return key value map + */ + public Map<String, JsonNode> getParams() { + return params; + } + + @JsonProperty("params") + public void setParams(Map<String, JsonNode> params) { + this.params = params; + } + + /** + * Reserved for future use. + * + * @return key value map + */ + public Map<String, String> getPublishAttributes() { + return publishAttributes; + } + + @JsonProperty("publishAttributes") + public void setPublishAttributes(Map<String, String> publishAttributes) { + this.publishAttributes = publishAttributes; + } + + } + + @JsonProperty("linkConfig") + public void setLinkConfig(List<LinkConfig> links2) { + this.links = links2; + } + + /** + * Reserved for future use. + * + * @return list of configured link configuration + */ + public List<LinkConfig> getLinkConfig() { + return links; + } + + /** + * Reserved for future use. + */ + public static class LinkConfig { + protected String type; + protected Boolean allowed; + protected DeviceId dpid1; + protected DeviceId dpid2; + protected String nodeDpid1; + protected String nodeDpid2; + protected Map<String, JsonNode> params; + protected Map<String, String> publishAttributes; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Boolean isAllowed() { + return allowed; + } + + public void setAllowed(Boolean allowed) { + this.allowed = allowed; + } + + public String getNodeDpid1() { + return nodeDpid1; + } + + // mapper sets both long and string fields for dpid + public void setNodeDpid1(String nodeDpid1) { + this.nodeDpid1 = nodeDpid1; + this.dpid1 = DeviceId.deviceId(nodeDpid1); + } + + public String getNodeDpid2() { + return nodeDpid2; + } + + // mapper sets both long and string fields for dpid + public void setNodeDpid2(String nodeDpid2) { + this.nodeDpid2 = nodeDpid2; + this.dpid2 = DeviceId.deviceId(nodeDpid2); + } + + public DeviceId getDpid1() { + return dpid1; + } + + public void setDpid1(DeviceId dpid1) { + this.dpid1 = dpid1; + this.nodeDpid1 = dpid1.toString(); + } + + public DeviceId getDpid2() { + return dpid2; + } + + public void setDpid2(DeviceId dpid2) { + this.dpid2 = dpid2; + this.nodeDpid2 = dpid2.toString(); + } + + public Map<String, JsonNode> getParams() { + return params; + } + + public void setParams(Map<String, JsonNode> params) { + this.params = params; + } + + public Map<String, String> getPublishAttributes() { + return publishAttributes; + } + + public void setPublishAttributes(Map<String, String> publishAttributes) { + this.publishAttributes = publishAttributes; + } + } +} + diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigException.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigException.java new file mode 100644 index 00000000..91942216 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigException.java @@ -0,0 +1,200 @@ +package org.onosproject.segmentrouting.config; + +import org.onosproject.net.DeviceId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * NetworkConfigExceptions specifies a set of unchecked runtime exceptions that + * can be thrown by the {@link NetworkConfigManager}. It indicates errors that + * must be fixed in the config file before controller execution can proceed. + */ +public class NetworkConfigException extends RuntimeException { + + private static final long serialVersionUID = 4959684709803000652L; + protected static final Logger log = LoggerFactory + .getLogger(NetworkConfigException.class); + + /** + * Exception for duplicate device identifier configuration. + */ + public static class DuplicateDpid extends RuntimeException { + private static final long serialVersionUID = 5491113234592145335L; + + public DuplicateDpid(DeviceId dpid) { + super(); + log.error("Duplicate dpid found in switch-config Dpid:{}", + dpid); + } + } + + /** + * Exception for duplicate device name configuration. + */ + public static class DuplicateName extends RuntimeException { + private static final long serialVersionUID = -4090171438031376129L; + + public DuplicateName(String name) { + super(); + log.error("Duplicate name found in switch-config name:{}", name); + } + } + + /** + * Exception for unspecified device identifier for a switch. + */ + public static class DpidNotSpecified extends RuntimeException { + private static final long serialVersionUID = -8494418855597117254L; + + public DpidNotSpecified(String name) { + super(); + log.error("Dpid not specified for switch-config name:{}", name); + } + } + + /** + * Exception for unspecified device name for a switch. + */ + public static class NameNotSpecified extends RuntimeException { + private static final long serialVersionUID = -3518881744110422891L; + + public NameNotSpecified(DeviceId dpid) { + super(); + log.error("Name not specified for switch-config dpid:{}", + dpid); + } + } + + /** + * Exception for unspecified device type for a switch. + */ + public static class SwitchTypeNotSpecified extends RuntimeException { + private static final long serialVersionUID = 2527453336226053753L; + + public SwitchTypeNotSpecified(DeviceId dpid) { + super(); + log.error("Switch type not specified for switch-config dpid:{}", + dpid); + } + } + + /** + * Exception for unknown device type configured for a switch. + */ + public static class UnknownSwitchType extends RuntimeException { + private static final long serialVersionUID = 7758418165512249170L; + + public UnknownSwitchType(String type, String name) { + super(); + log.error("Unknown switch type {} for switch name:{}", type, name); + } + } + + /** + * Exception for missing required parameter configuration for a switch. + */ + public static class ParamsNotSpecified extends RuntimeException { + private static final long serialVersionUID = 6247582323691265513L; + + public ParamsNotSpecified(String name) { + super(); + log.error("Params required - not specified for switch:{}", name); + } + } + + /** + * Reserved for future use. + */ + public static class LinkTypeNotSpecified extends RuntimeException { + private static final long serialVersionUID = -2089470389588542215L; + + public LinkTypeNotSpecified(String dpid1, String dpid2) { + super(); + log.error("Link type not specified for link-config between " + + "dpid1:{} and dpid2:{}", dpid1, dpid2); + } + } + + /** + * Reserved for future use. + */ + public static class LinkDpidNotSpecified extends RuntimeException { + private static final long serialVersionUID = -5701825916378616004L; + + public LinkDpidNotSpecified(String dpid1, String dpid2) { + super(); + if (dpid1 == null) { + log.error("nodeDpid1 not specified for link-config "); + } + if (dpid2 == null) { + log.error("nodeDpid2 not specified for link-config "); + } + } + } + + /** + * Reserved for future use. + */ + public static class LinkForUnknownSwitchConfig extends RuntimeException { + private static final long serialVersionUID = -2910458439881964094L; + + public LinkForUnknownSwitchConfig(String dpid) { + super(); + log.error("Link configuration was specified for a switch-dpid {} " + + "that has not been configured", dpid); + } + } + + /** + * Reserved for future use. + */ + public static class UnknownLinkType extends RuntimeException { + private static final long serialVersionUID = -5505376193106542305L; + + public UnknownLinkType(String linktype, String dpid1, String dpid2) { + super(); + log.error("unknown link type {} for links between dpid1:{} " + + "and dpid2:{}", linktype, dpid1, dpid2); + } + } + + /** + * Exception for generic configuration errors. + */ + public static class ErrorConfig extends RuntimeException { + private static final long serialVersionUID = -2827406314700193147L; + + public ErrorConfig(String errorMsg) { + super(); + log.error(errorMsg); + } + + } + + /** + * Reserved for future use. + */ + public static class SwitchDpidNotConverted extends RuntimeException { + private static final long serialVersionUID = 5640347104590170426L; + + public SwitchDpidNotConverted(String name) { + super(); + log.error("Switch dpid specified as a HexString {} does not match " + + "with long value", name); + } + } + + /** + * Reserved for future use. + */ + public static class LinkDpidNotConverted extends RuntimeException { + private static final long serialVersionUID = 2397245646094080774L; + + public LinkDpidNotConverted(String dpid1, String dpid2) { + log.error("Dpids expressed as HexStrings for links between dpid1:{} " + + "and dpid2:{} do not match with long values", dpid1, dpid2); + } + } + +} + diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigManager.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigManager.java new file mode 100644 index 00000000..44e4f1c6 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigManager.java @@ -0,0 +1,323 @@ +package org.onosproject.segmentrouting.config; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.segmentrouting.config.NetworkConfig.LinkConfig; +import org.onosproject.segmentrouting.config.NetworkConfig.SwitchConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * NetworkConfigManager manages all network configuration for switches, links + * and any other state that needs to be configured for correct network + * operation. + * + */ +public class NetworkConfigManager implements NetworkConfigService { + protected static final Logger log = LoggerFactory + .getLogger(NetworkConfigManager.class); + private static final String CONFIG_DIR = "../config"; + private static final String DEFAULT_CONFIG_FILE = "segmentrouting.conf"; + private final String configFileName = DEFAULT_CONFIG_FILE; + /** + * JSON Config file needs to use one of the following types for defining the + * kind of switch or link it wishes to configure. + */ + public static final String SEGMENT_ROUTER = "Router_SR"; + + public static final String PKT_LINK = "pktLink"; + + NetworkConfig networkConfig; + private ConcurrentMap<DeviceId, SwitchConfig> configuredSwitches; + private ConcurrentMap<Link, LinkConfig> configuredLinks; + private Map<String, DeviceId> nameToDpid; + + @Override + public SwitchConfigStatus checkSwitchConfig(DeviceId dpid) { + SwitchConfig swc = configuredSwitches.get(dpid); + if (networkConfig.getRestrictSwitches()) { + // default deny behavior + if (swc == null) { + // switch is not configured - we deny this switch + return new SwitchConfigStatus(NetworkConfigState.DENY, null, + "Switch not configured, in network denying switches by default."); + } + if (swc.isAllowed()) { + // switch is allowed in config, return configured attributes + return new SwitchConfigStatus(NetworkConfigState.ACCEPT_ADD, swc); + } else { + // switch has been configured off (administratively down) + return new SwitchConfigStatus(NetworkConfigState.DENY, null, + "Switch configured down (allowed=false)."); + } + } else { + // default allow behavior + if (swc == null) { + // no config to add + return new SwitchConfigStatus(NetworkConfigState.ACCEPT, null); + } + if (swc.isAllowed()) { + // switch is allowed in config, return configured attributes + return new SwitchConfigStatus(NetworkConfigState.ACCEPT_ADD, swc); + } else { + // switch has been configured off (administratively down) + return new SwitchConfigStatus(NetworkConfigState.DENY, null, + "Switch configured down (allowed=false)."); + } + } + + } + + @Override + public LinkConfigStatus checkLinkConfig(Link linkTuple) { + LinkConfig lkc = getConfiguredLink(linkTuple); + // links are always disallowed if any one of the nodes that make up the + // link are disallowed + DeviceId linkNode1 = linkTuple.src().deviceId(); + SwitchConfigStatus scs1 = checkSwitchConfig(linkNode1); + if (scs1.getConfigState() == NetworkConfigState.DENY) { + return new LinkConfigStatus(NetworkConfigState.DENY, null, + "Link-node: " + linkNode1 + " denied by config: " + scs1.getMsg()); + } + DeviceId linkNode2 = linkTuple.dst().deviceId(); + SwitchConfigStatus scs2 = checkSwitchConfig(linkNode2); + if (scs2.getConfigState() == NetworkConfigState.DENY) { + return new LinkConfigStatus(NetworkConfigState.DENY, null, + "Link-node: " + linkNode2 + " denied by config: " + scs2.getMsg()); + } + if (networkConfig.getRestrictLinks()) { + // default deny behavior + if (lkc == null) { + // link is not configured - we deny this link + return new LinkConfigStatus(NetworkConfigState.DENY, null, + "Link not configured, in network denying links by default."); + } + if (lkc.isAllowed()) { + // link is allowed in config, return configured attributes + return new LinkConfigStatus(NetworkConfigState.ACCEPT_ADD, lkc); + } else { + // link has been configured off (administratively down) + return new LinkConfigStatus(NetworkConfigState.DENY, null, + "Link configured down (allowed=false)."); + } + } else { + // default allow behavior + if (lkc == null) { + // no config to add + return new LinkConfigStatus(NetworkConfigState.ACCEPT, null); + } + if (lkc.isAllowed()) { + // link is allowed in config, return configured attributes + return new LinkConfigStatus(NetworkConfigState.ACCEPT_ADD, lkc); + } else { + // link has been configured off (administratively down) + return new LinkConfigStatus(NetworkConfigState.DENY, null, + "Link configured down (allowed=false)."); + } + } + + } + + @Override + public List<SwitchConfig> getConfiguredAllowedSwitches() { + List<SwitchConfig> allowed = new ArrayList<SwitchConfig>(); + for (SwitchConfig swc : configuredSwitches.values()) { + if (swc.isAllowed()) { + allowed.add(swc); + } + } + return allowed; + } + + @Override + public List<LinkConfig> getConfiguredAllowedLinks() { + List<LinkConfig> allowed = new ArrayList<LinkConfig>(); + for (LinkConfig lkc : configuredLinks.values()) { + if (lkc.isAllowed()) { + allowed.add(lkc); + } + } + return allowed; + } + + @Override + public DeviceId getDpidForName(String name) { + if (nameToDpid.get(name) != null) { + return nameToDpid.get(name); + } + return null; + } + + // ************** + // Private methods + // ************** + + private void loadNetworkConfig() { + File configFile = new File(CONFIG_DIR, configFileName); + ObjectMapper mapper = new ObjectMapper(); + networkConfig = new NetworkConfig(); + + try { + networkConfig = mapper.readValue(configFile, + NetworkConfig.class); + } catch (JsonParseException e) { + String err = String.format("JsonParseException while loading network " + + "config from file: %s: %s", configFileName, + e.getMessage()); + throw new NetworkConfigException.ErrorConfig(err); + } catch (JsonMappingException e) { + String err = String.format( + "JsonMappingException while loading network config " + + "from file: %s: %s", + configFileName, + e.getMessage()); + throw new NetworkConfigException.ErrorConfig(err); + } catch (IOException e) { + String err = String.format("IOException while loading network config " + + "from file: %s %s", configFileName, e.getMessage()); + throw new NetworkConfigException.ErrorConfig(err); + } + + log.info("Network config specifies: {} switches and {} links", + (networkConfig.getRestrictSwitches()) + ? networkConfig.getSwitchConfig().size() : "default allow", + (networkConfig.getRestrictLinks()) + ? networkConfig.getLinkConfig().size() : "default allow"); + } + + private void parseNetworkConfig() { + List<SwitchConfig> swConfList = networkConfig.getSwitchConfig(); + List<LinkConfig> lkConfList = networkConfig.getLinkConfig(); + validateSwitchConfig(swConfList); + createTypeSpecificSwitchConfig(swConfList); + validateLinkConfig(lkConfList); + createTypeSpecificLinkConfig(lkConfList); + // TODO validate reachability matrix 'names' for configured dpids + } + + private void createTypeSpecificSwitchConfig(List<SwitchConfig> swConfList) { + for (SwitchConfig swc : swConfList) { + nameToDpid.put(swc.getName(), swc.getDpid()); + String swtype = swc.getType(); + switch (swtype) { + case SEGMENT_ROUTER: + SwitchConfig sr = new SegmentRouterConfig(swc); + configuredSwitches.put(sr.getDpid(), sr); + break; + default: + throw new NetworkConfigException.UnknownSwitchType(swtype, + swc.getName()); + } + } + } + + private void createTypeSpecificLinkConfig(List<LinkConfig> lkConfList) { + for (LinkConfig lkc : lkConfList) { + String lktype = lkc.getType(); + switch (lktype) { + case PKT_LINK: + PktLinkConfig pk = new PktLinkConfig(lkc); + for (Link lt : pk.getLinkTupleList()) { + configuredLinks.put(lt, pk); + } + break; + default: + throw new NetworkConfigException.UnknownLinkType(lktype, + lkc.getNodeDpid1(), lkc.getNodeDpid2()); + } + } + } + + private void validateSwitchConfig(List<SwitchConfig> swConfList) { + Set<DeviceId> swDpids = new HashSet<DeviceId>(); + Set<String> swNames = new HashSet<String>(); + for (SwitchConfig swc : swConfList) { + if (swc.getNodeDpid() == null || swc.getDpid() == null) { + throw new NetworkConfigException.DpidNotSpecified(swc.getName()); + } + // ensure both String and DeviceId values of dpid are set + if (!swc.getDpid().equals(DeviceId.deviceId(swc.getNodeDpid()))) { + throw new NetworkConfigException.SwitchDpidNotConverted( + swc.getName()); + } + if (swc.getName() == null) { + throw new NetworkConfigException.NameNotSpecified(swc.getDpid()); + } + if (swc.getType() == null) { + throw new NetworkConfigException.SwitchTypeNotSpecified( + swc.getDpid()); + } + if (!swDpids.add(swc.getDpid())) { + throw new NetworkConfigException.DuplicateDpid(swc.getDpid()); + } + if (!swNames.add(swc.getName())) { + throw new NetworkConfigException.DuplicateName(swc.getName()); + } + // TODO Add more validations + } + } + + private void validateLinkConfig(List<LinkConfig> lkConfList) { + for (LinkConfig lkc : lkConfList) { + if (lkc.getNodeDpid1() == null || lkc.getNodeDpid2() == null) { + throw new NetworkConfigException.LinkDpidNotSpecified( + lkc.getNodeDpid1(), lkc.getNodeDpid2()); + } + // ensure both String and Long values are set + if (!lkc.getDpid1().equals(DeviceId.deviceId(lkc.getNodeDpid1())) || + !lkc.getDpid2().equals(DeviceId.deviceId(lkc.getNodeDpid2()))) { + throw new NetworkConfigException.LinkDpidNotConverted( + lkc.getNodeDpid1(), lkc.getNodeDpid2()); + } + if (lkc.getType() == null) { + throw new NetworkConfigException.LinkTypeNotSpecified( + lkc.getNodeDpid1(), lkc.getNodeDpid2()); + } + if (configuredSwitches.get(lkc.getDpid1()) == null) { + throw new NetworkConfigException.LinkForUnknownSwitchConfig( + lkc.getNodeDpid1()); + } + if (configuredSwitches.get(lkc.getDpid2()) == null) { + throw new NetworkConfigException.LinkForUnknownSwitchConfig( + lkc.getNodeDpid2()); + } + // TODO add more validations + } + + } + + private LinkConfig getConfiguredLink(Link linkTuple) { + LinkConfig lkc = null; + // first try the unidirectional link with the ports assigned + lkc = configuredLinks.get(linkTuple); + return lkc; + } + + + /** + * Initializes the network configuration manager module by + * loading and parsing the network configuration file. + */ + public void init() { + loadNetworkConfig(); + configuredSwitches = new ConcurrentHashMap<DeviceId, SwitchConfig>(); + configuredLinks = new ConcurrentHashMap<Link, LinkConfig>(); + nameToDpid = new HashMap<String, DeviceId>(); + parseNetworkConfig(); + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigService.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigService.java new file mode 100644 index 00000000..56855271 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/NetworkConfigService.java @@ -0,0 +1,256 @@ +package org.onosproject.segmentrouting.config; + +import java.util.List; + +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.segmentrouting.config.NetworkConfig.LinkConfig; +import org.onosproject.segmentrouting.config.NetworkConfig.SwitchConfig; + +/** + * Exposes methods to retrieve network configuration. + * + * TODO: currently only startup-configuration is exposed and such configuration + * cannot be changed at runtime. Need to add runtime support for changes to + * configuration (via REST/CLI) in future releases. + * + * TODO: return immutable objects or defensive copies of network config so that + * users of this API do not inadvertently or maliciously change network config. + * + * @deprecated in Drake; see org.onosproject.net.config + */ +@Deprecated +public interface NetworkConfigService { + + /** + * Suggests the action to be taken by the caller given the configuration + * associated with the queried network-object (eg. switch, link etc.). + */ + enum NetworkConfigState { + /** + * Associated network object has been configured to not be allowed in + * the network. + */ + DENY, + + /** + * Associated network object has been configured to be allowed in the + * network. + */ + ACCEPT, + + /** + * Associated network object has been configured to be allowed in the + * network. In addition, there are configured parameters that should be + * added to the object. + */ + ACCEPT_ADD, + } + + /** + * Returns the configuration outcome (accept, deny etc.), and any configured + * parameters to the caller, in response to a query for the configuration + * associated with a switch. + */ + class SwitchConfigStatus { + private NetworkConfigState configState; + private SwitchConfig switchConfig; + private String msg; + + SwitchConfigStatus(NetworkConfigState configState, + SwitchConfig switchConfig, String msg) { + this.configState = configState; + this.switchConfig = switchConfig; + this.msg = msg; + } + + SwitchConfigStatus(NetworkConfigState configState, + SwitchConfig switchConfig) { + this.configState = configState; + this.switchConfig = switchConfig; + this.msg = ""; + } + + /** + * Returns the configuration state for the switch. + * + * @return non-null NetworkConfigState + */ + public NetworkConfigState getConfigState() { + return configState; + } + + /** + * Returns the switch configuration, which may be null if no + * configuration exists, or if the configuration state disallows the + * switch. + * + * @return SwitchConfig, the switch configuration, or null + */ + public SwitchConfig getSwitchConfig() { + return switchConfig; + } + + /** + * User readable string typically used to specify the reason why a + * switch is being disallowed. + * + * @return A non-null but possibly empty String + */ + public String getMsg() { + return msg; + } + + } + + /** + * Reserved for future use. + * + * Returns the configuration outcome (accept, deny etc.), and any configured + * parameters to the caller, in response to a query for the configuration + * associated with a link. + */ + class LinkConfigStatus { + private NetworkConfigState configState; + private LinkConfig linkConfig; + private String msg; + + LinkConfigStatus(NetworkConfigState configState, + LinkConfig linkConfig, String msg) { + this.configState = configState; + this.linkConfig = linkConfig; + this.msg = msg; + } + + LinkConfigStatus(NetworkConfigState configState, + LinkConfig linkConfig) { + this.configState = configState; + this.linkConfig = linkConfig; + this.msg = ""; + } + + /** + * Returns the configuration state for the link. + * + * @return non-null NetworkConfigState + */ + public NetworkConfigState getConfigState() { + return configState; + } + + /** + * Returns the link configuration, which may be null if no configuration + * exists, or if the configuration state disallows the link. + * + * @return SwitchConfig, the switch configuration, or null + */ + public LinkConfig getLinkConfig() { + return linkConfig; + } + + /** + * User readable string typically used to specify the reason why a link + * is being disallowed. + * + * @return msg A non-null but possibly empty String + */ + public String getMsg() { + return msg; + } + + } + + /** + * Checks the switch configuration (if any) associated with the 'dpid'. + * Determines if the switch should be allowed or denied according to + * configuration rules. + * + * The method always returns a non-null SwitchConfigStatus. The enclosed + * ConfigState contains the result of the check. The enclosed SwitchConfig + * may or may not be null, depending on the outcome of the check. + * + * @param dpid device id of the switch to be queried + * @return SwitchConfigStatus with outcome of check and associated config. + */ + SwitchConfigStatus checkSwitchConfig(DeviceId dpid); + + /** + * Reserved for future use. + * + * Checks the link configuration (if any) associated with the 'link'. + * Determines if the link should be allowed or denied according to + * configuration rules. Note that the 'link' is a unidirectional link which + * checked against configuration that is typically defined for a + * bidirectional link. The caller may make a second call if it wishes to + * check the 'reverse' direction. + * + * Also note that the configuration may not specify ports for a given + * bidirectional link. In such cases, the configuration applies to all links + * between the two switches. This method will check the given 'link' against + * such configuration. + + * The method always returns a non-null LinkConfigStatus. The enclosed + * ConfigState contains the result of the check. The enclosed LinkConfig may + * or may not be null, depending on the outcome of the check. + * + * @param linkTuple unidirectional link to be queried + * @return LinkConfigStatus with outcome of check and associated config. + */ + LinkConfigStatus checkLinkConfig(Link linkTuple); + + /** + * Retrieves a list of switches that have been configured, and have been + * determined to be 'allowed' in the network, according to configuration + * rules. + * + * Note that it is possible that there are other switches that are allowed + * in the network that have NOT been configured. Such switches will not be a + * part of the returned list. + * + * Also note that it is possible that some switches will not be discovered + * and the only way the controller can know about these switches is via + * configuration. Such switches will be included in this list. It is up to + * the caller to determine which SwitchConfig applies to non-discovered + * switches. + * + * @return a non-null List of SwitchConfig which may be empty + */ + List<SwitchConfig> getConfiguredAllowedSwitches(); + + /** + * Reserved for future use. + * + * Retrieves a list of links that have been configured, and have been + * determined to be 'allowed' in the network, according to configuration + * rules. + * + * Note that it is possible that there are other links that are allowed in + * the network that have NOT been configured. Such links will not be a part + * of the returned list. + * + * Also note that it is possible that some links will not be discovered and + * the only way the controller can know about these links is via + * configuration. Such links will be included in this list. It is up to the + * caller to determine which LinkConfig applies to non-discovered links. + * + * In addition, note that the LinkConfig applies to the configured + * bi-directional link, which may or may not have declared ports. The + * associated unidirectional LinkTuple can be retrieved from the + * getLinkTupleList() method in the LinkConfig object. + * + * @return a non-null List of LinkConfig which may be empty + */ + List<LinkConfig> getConfiguredAllowedLinks(); + + /** + * Retrieves the Dpid associated with a 'name' for a configured switch + * object. This method does not check of the switches are 'allowed' by + * config. + * + * @param name device name + * @return the Dpid corresponding to a given 'name', or null if no + * configured switch was found for the given 'name'. + */ + DeviceId getDpidForName(String name); + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/PktLinkConfig.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/PktLinkConfig.java new file mode 100644 index 00000000..c66ac3c7 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/PktLinkConfig.java @@ -0,0 +1,144 @@ +package org.onosproject.segmentrouting.config; + +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.onosproject.net.Link; +import org.onosproject.segmentrouting.config.NetworkConfig.LinkConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Reserved for future use. + * Configuration for a link between two packet-switches. + */ +public class PktLinkConfig extends LinkConfig { + protected static final Logger log = LoggerFactory + .getLogger(PktLinkConfig.class); + private int port1; + private int port2; + private String nodeName1; + private String nodeName2; + private List<Link> linkTupleList; + + public PktLinkConfig(LinkConfig lkc) { + nodeDpid1 = lkc.getNodeDpid1(); + nodeDpid2 = lkc.getNodeDpid2(); + dpid1 = lkc.getDpid1(); + dpid2 = lkc.getDpid2(); + type = lkc.getType(); + allowed = lkc.isAllowed(); + params = lkc.getParams(); + publishAttributes = new ConcurrentHashMap<String, String>(); + parseParams(); + validateParams(); + setPublishAttributes(); + } + + // ******************** + // Packet Link Configuration + // ******************** + + public int getPort1() { + return port1; + } + + public void setPort1(int port1) { + this.port1 = port1; + } + + public int getPort2() { + return port2; + } + + public void setPort2(int port2) { + this.port2 = port2; + } + + public String getNodeName1() { + return nodeName1; + } + + public void setNodeName1(String nodeName1) { + this.nodeName1 = nodeName1; + } + + public String getNodeName2() { + return nodeName2; + } + + public void setNodeName2(String nodeName2) { + this.nodeName2 = nodeName2; + } + + /** + * Returns the two unidirectional links corresponding to the packet-link + * configuration. It is possible that the ports in the LinkTuple have + * portnumber '0', implying that the configuration applies to all links + * between the two switches. + * + * @return a list of LinkTuple with exactly 2 unidirectional links + */ + public List<Link> getLinkTupleList() { + return linkTupleList; + } + + private void setPublishAttributes() { + + } + + private void parseParams() { + if (params == null) { + throw new PktLinkParamsNotSpecified(nodeDpid1, nodeDpid2); + } + Set<Entry<String, JsonNode>> m = params.entrySet(); + for (Entry<String, JsonNode> e : m) { + String key = e.getKey(); + JsonNode j = e.getValue(); + if (key.equals("nodeName1")) { + setNodeName1(j.asText()); + } else if (key.equals("nodeName2")) { + setNodeName2(j.asText()); + } else if (key.equals("port1")) { + setPort1(j.asInt()); + } else if (key.equals("port2")) { + setPort2(j.asInt()); + } else { + throw new UnknownPktLinkConfig(key, nodeDpid1, nodeDpid2); + } + } + } + + private void validateParams() { + // TODO - wrong-names, duplicate links, + // duplicate use of port, is switch-allowed for which link is allowed? + // valid port numbers + } + + public static class PktLinkParamsNotSpecified extends RuntimeException { + private static final long serialVersionUID = 6247582323691265513L; + + public PktLinkParamsNotSpecified(String dpidA, String dpidB) { + super(); + log.error("Params required for packet link - not specified " + + "for link between switch1:{} and switch2:{}", + dpidA, dpidB); + } + } + + public static class UnknownPktLinkConfig extends RuntimeException { + private static final long serialVersionUID = -5750132094884129179L; + + public UnknownPktLinkConfig(String key, String dpidA, String dpidB) { + super(); + log.error("Unknown packet-link config {} for link between" + + " dpid1: {} and dpid2: {}", key, + dpidA, dpidB); + } + } + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRouterConfig.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRouterConfig.java new file mode 100644 index 00000000..4775c77f --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/SegmentRouterConfig.java @@ -0,0 +1,430 @@ +package org.onosproject.segmentrouting.config; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.onosproject.net.DeviceId; +import org.onosproject.segmentrouting.config.NetworkConfig.SwitchConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Manages additional configuration for switches configured as Segment Routers. + */ +public class SegmentRouterConfig extends SwitchConfig { + protected static final Logger log = LoggerFactory + .getLogger(SegmentRouterConfig.class); + private String routerIp; + private String routerMac; + private int nodeSid; + private boolean isEdgeRouter; + private List<AdjacencySid> adjacencySids; + private List<Subnet> subnets; + + public static final String ROUTER_IP = "routerIp"; + public static final String ROUTER_MAC = "routerMac"; + public static final String NODE_SID = "nodeSid"; + public static final String ADJACENCY_SIDS = "adjacencySids"; + public static final String SUBNETS = "subnets"; + public static final String ISEDGE = "isEdgeRouter"; + private static final int SRGB_MAX = 1000; + + /** + * Parses and validates the additional configuration parameters applicable + * to segment routers. + * + * @param swc switch configuration + */ + public SegmentRouterConfig(SwitchConfig swc) { + this.setName(swc.getName()); + this.setDpid(swc.getDpid()); + this.setType(swc.getType()); + this.setLatitude(swc.getLatitude()); + this.setLongitude(swc.getLongitude()); + this.setParams(swc.getParams()); + this.setAllowed(swc.isAllowed()); + publishAttributes = new ConcurrentHashMap<String, String>(); + adjacencySids = new ArrayList<AdjacencySid>(); + subnets = new ArrayList<Subnet>(); + parseParams(); + validateParams(); + setPublishAttributes(); + } + + /** + * Returns the configured segment router IP address. + * + * @return ip address in string format + */ + public String getRouterIp() { + return routerIp; + } + + public void setRouterIp(String routerIp) { + this.routerIp = routerIp; + } + + /** + * Returns the configured segment router mac address. + * + * @return mac address in string format + */ + public String getRouterMac() { + return routerMac; + } + + public void setRouterMac(String routerMac) { + this.routerMac = routerMac; + } + + /** + * Returns the configured sID for a segment router. + * + * @return segment identifier + */ + public int getNodeSid() { + return nodeSid; + } + + public void setNodeSid(int nodeSid) { + this.nodeSid = nodeSid; + } + + /** + * Returns the flag that indicates the configured segment router + * is edge or backbone router. + * + * @return boolean + */ + public boolean isEdgeRouter() { + return isEdgeRouter; + } + + public void setIsEdgeRouter(boolean isEdge) { + this.isEdgeRouter = isEdge; + } + + /** + * Class representing segment router adjacency identifier. + */ + public static class AdjacencySid { + private int adjSid; + private List<Integer> ports; + + public AdjacencySid(int adjSid, List<Integer> ports) { + this.ports = ports; + this.adjSid = adjSid; + } + + /** + * Returns the list of ports part of a segment + * router adjacency identifier. + * + * @return list of integers + */ + public List<Integer> getPorts() { + return ports; + } + + public void setPorts(List<Integer> ports) { + this.ports = ports; + } + + /** + * Returns the configured adjacency id of a segment router. + * + * @return integer + */ + public int getAdjSid() { + return adjSid; + } + + public void setAdjSid(int adjSid) { + this.adjSid = adjSid; + } + } + + /** + * Returns the configured adjacent segment IDs for a segment router. + * + * @return list of adjacency identifier + */ + public List<AdjacencySid> getAdjacencySids() { + return adjacencySids; + } + + public void setAdjacencySids(List<AdjacencySid> adjacencySids) { + this.adjacencySids = adjacencySids; + } + + /** + * Class representing a subnet attached to a segment router. + */ + public static class Subnet { + private int portNo; + private String subnetIp; + + public Subnet(int portNo, String subnetIp) { + this.portNo = portNo; + this.subnetIp = subnetIp; + } + + /** + * Returns the port number of segment router on + * which subnet is attached. + * + * @return integer + */ + public int getPortNo() { + return portNo; + } + + public void setPortNo(int portNo) { + this.portNo = portNo; + } + + /** + * Returns the configured subnet address. + * + * @return subnet ip address in string format + */ + public String getSubnetIp() { + return subnetIp; + } + + public void setSubnetIp(String subnetIp) { + this.subnetIp = subnetIp; + } + } + + /** + * Returns the configured subnets for a segment router. + * + * @return list of subnets + */ + public List<Subnet> getSubnets() { + return subnets; + } + + public void setSubnets(List<Subnet> subnets) { + this.subnets = subnets; + } + + // ******************** + // Helper methods + // ******************** + + private void parseParams() { + if (params == null) { + throw new NetworkConfigException.ParamsNotSpecified(name); + } + + Set<Entry<String, JsonNode>> m = params.entrySet(); + for (Entry<String, JsonNode> e : m) { + String key = e.getKey(); + JsonNode j = e.getValue(); + if (key.equals("routerIp")) { + setRouterIp(j.asText()); + } else if (key.equals("routerMac")) { + setRouterMac(j.asText()); + } else if (key.equals("nodeSid")) { + setNodeSid(j.asInt()); + } else if (key.equals("isEdgeRouter")) { + setIsEdgeRouter(j.asBoolean()); + } else if (key.equals("adjacencySids") || key.equals("subnets")) { + getInnerParams(j, key); + } else { + throw new UnknownSegmentRouterConfig(key, dpid); + } + } + } + + private void getInnerParams(JsonNode j, String innerParam) { + Iterator<JsonNode> innerList = j.elements(); + while (innerList.hasNext()) { + Iterator<Entry<String, JsonNode>> f = innerList.next().fields(); + int portNo = -1; + int adjSid = -1; + String subnetIp = null; + List<Integer> ports = null; + while (f.hasNext()) { + Entry<String, JsonNode> fe = f.next(); + if (fe.getKey().equals("portNo")) { + portNo = fe.getValue().asInt(); + } else if (fe.getKey().equals("adjSid")) { + adjSid = fe.getValue().asInt(); + } else if (fe.getKey().equals("subnetIp")) { + subnetIp = fe.getValue().asText(); + } else if (fe.getKey().equals("ports")) { + if (fe.getValue().isArray()) { + Iterator<JsonNode> i = fe.getValue().elements(); + ports = new ArrayList<Integer>(); + while (i.hasNext()) { + ports.add(i.next().asInt()); + } + } + } else { + throw new UnknownSegmentRouterConfig(fe.getKey(), dpid); + } + } + if (innerParam.equals("adjacencySids")) { + AdjacencySid ads = new AdjacencySid(adjSid, ports); + adjacencySids.add(ads); + } else { + Subnet sip = new Subnet(portNo, subnetIp); + subnets.add(sip); + } + } + } + + private void validateParams() { + if (routerIp == null) { + throw new IpNotSpecified(dpid); + } + if (routerMac == null) { + throw new MacNotSpecified(dpid); + } + if (isEdgeRouter && subnets.isEmpty()) { + throw new SubnetNotSpecifiedInEdgeRouter(dpid); + } + if (!isEdgeRouter && !subnets.isEmpty()) { + throw new SubnetSpecifiedInBackboneRouter(dpid); + } + if (nodeSid > SRGB_MAX) { + throw new NodeLabelNotInSRGB(nodeSid, dpid); + } + for (AdjacencySid as : adjacencySids) { + int label = as.getAdjSid(); + List<Integer> plist = as.getPorts(); + if (label <= SRGB_MAX) { + throw new AdjacencyLabelInSRGB(label, dpid); + } + if (plist.size() <= 1) { + throw new AdjacencyLabelNotEnoughPorts(label, dpid); + } + } + + + // TODO more validations + } + + /** + * Setting publishAttributes implies that this is the configuration that + * will be added to Topology.Switch object before it is published on the + * channel to other controller instances. + */ + private void setPublishAttributes() { + publishAttributes.put(ROUTER_IP, routerIp); + publishAttributes.put(ROUTER_MAC, routerMac); + publishAttributes.put(NODE_SID, String.valueOf(nodeSid)); + publishAttributes.put(ISEDGE, String.valueOf(isEdgeRouter)); + ObjectMapper mapper = new ObjectMapper(); + try { + publishAttributes.put(ADJACENCY_SIDS, + mapper.writeValueAsString(adjacencySids)); + publishAttributes.put(SUBNETS, + mapper.writeValueAsString(subnets)); + } catch (JsonProcessingException e) { + log.error("Error while writing SR config: {}", e.getCause()); + } catch (IOException e) { + log.error("Error while writing SR config: {}", e.getCause()); + } + } + + // ******************** + // Exceptions + // ******************** + + public static class IpNotSpecified extends RuntimeException { + private static final long serialVersionUID = -3001502553646331686L; + + public IpNotSpecified(DeviceId dpid) { + super(); + log.error("Router IP address not specified for SR config dpid:{}", + dpid); + } + } + + public static class MacNotSpecified extends RuntimeException { + private static final long serialVersionUID = -5850132094884129179L; + + public MacNotSpecified(DeviceId dpid) { + super(); + log.error("Router Mac address not specified for SR config dpid:{}", + dpid); + } + } + + public static class UnknownSegmentRouterConfig extends RuntimeException { + private static final long serialVersionUID = -5750132094884129179L; + + public UnknownSegmentRouterConfig(String key, DeviceId dpid) { + super(); + log.error("Unknown Segment Router config {} in dpid: {}", key, + dpid); + } + } + + public static class SubnetNotSpecifiedInEdgeRouter extends RuntimeException { + private static final long serialVersionUID = -5855458472668581268L; + + public SubnetNotSpecifiedInEdgeRouter(DeviceId dpid) { + super(); + log.error("Subnet was not specified for edge router in dpid: {}", + dpid); + } + } + + public static class SubnetSpecifiedInBackboneRouter extends RuntimeException { + private static final long serialVersionUID = 1L; + + public SubnetSpecifiedInBackboneRouter(DeviceId dpid) { + super(); + log.error("Subnet was specified in backbone router in dpid: {}", + dpid); + } + } + + public static class NodeLabelNotInSRGB extends RuntimeException { + private static final long serialVersionUID = -8482670903748519526L; + + public NodeLabelNotInSRGB(int label, DeviceId dpid) { + super(); + log.error("Node sif {} specified in not in global label-base " + + "in dpid: {}", label, + dpid); + } + } + + public static class AdjacencyLabelInSRGB extends RuntimeException { + private static final long serialVersionUID = -8482670903748519526L; + + public AdjacencyLabelInSRGB(int label, DeviceId dpid) { + super(); + log.error("Adjaceny label {} specified from global label-base " + + "in dpid: {}", label, + dpid); + } + } + + public static class AdjacencyLabelNotEnoughPorts extends RuntimeException { + private static final long serialVersionUID = -8482670903748519526L; + + public AdjacencyLabelNotEnoughPorts(int label, DeviceId dpid) { + super(); + log.error("Adjaceny label {} must be specified for at least 2 ports. " + + "Adjacency labels for single ports are auto-generated " + + "in dpid: {}", label, + dpid); + } + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/package-info.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/package-info.java new file mode 100644 index 00000000..fdae9c9e --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/config/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Segment routing network configuration mechanism. + */ +package org.onosproject.segmentrouting.config;
\ No newline at end of file diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultEdgeGroupHandler.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultEdgeGroupHandler.java new file mode 100644 index 00000000..41cf8acc --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultEdgeGroupHandler.java @@ -0,0 +1,180 @@ +/* + * Copyright 2015 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.segmentrouting.grouphandler; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.flowobjective.FlowObjectiveService; +import org.onosproject.net.link.LinkService; +import org.onosproject.store.service.EventuallyConsistentMap; + +/** + * Default ECMP group handler creation module for an edge device. + * This component creates a set of ECMP groups for every neighbor + * that this device is connected to. + * For example, consider a network of 4 devices: D0 (Segment ID: 100), + * D1 (Segment ID: 101), D2 (Segment ID: 102) and D3 (Segment ID: 103), + * where D0 and D3 are edge devices and D1 and D2 are transit devices. + * Assume device D0 is connected to 2 neighbors (D1 and D2 ). + * The following groups will be created in D0: + * 1) all ports to D1 + with no label push, + * 2) all ports to D1 + with label 102 pushed, + * 3) all ports to D1 + with label 103 pushed, + * 4) all ports to D2 + with no label push, + * 5) all ports to D2 + with label 101 pushed, + * 6) all ports to D2 + with label 103 pushed, + * 7) all ports to D1 and D2 + with label 103 pushed + */ +public class DefaultEdgeGroupHandler extends DefaultGroupHandler { + + protected DefaultEdgeGroupHandler(DeviceId deviceId, + ApplicationId appId, + DeviceProperties config, + LinkService linkService, + FlowObjectiveService flowObjService, + EventuallyConsistentMap< + NeighborSetNextObjectiveStoreKey, + Integer> nsNextObjStore) { + super(deviceId, appId, config, linkService, flowObjService, nsNextObjStore); + } + + @Override + public void createGroups() { + log.debug("Creating default groups " + + "for edge device {}", deviceId); + Set<DeviceId> neighbors = devicePortMap.keySet(); + if (neighbors == null || neighbors.isEmpty()) { + return; + } + + // Create all possible Neighbor sets from this router + Set<Set<DeviceId>> powerSet = getPowerSetOfNeighbors(neighbors); + log.trace("createGroupsAtEdgeRouter: The size of neighbor powerset " + + "for sw {} is {}", deviceId, powerSet.size()); + Set<NeighborSet> nsSet = new HashSet<NeighborSet>(); + for (Set<DeviceId> combo : powerSet) { + if (combo.isEmpty()) { + continue; + } + List<Integer> groupSegmentIds = + getSegmentIdsTobePairedWithNeighborSet(combo); + for (Integer sId : groupSegmentIds) { + NeighborSet ns = new NeighborSet(combo, sId); + log.trace("createGroupsAtEdgeRouter: sw {} " + + "combo {} sId {} ns {}", + deviceId, combo, sId, ns); + nsSet.add(ns); + } + } + log.trace("createGroupsAtEdgeRouter: The neighborset " + + "with label for sw {} is {}", + deviceId, nsSet); + + createGroupsFromNeighborsets(nsSet); + } + + @Override + protected void newNeighbor(Link newNeighborLink) { + log.debug("New Neighbor: Updating groups " + + "for edge device {}", deviceId); + // Recompute neighbor power set + addNeighborAtPort(newNeighborLink.dst().deviceId(), + newNeighborLink.src().port()); + // Compute new neighbor sets due to the addition of new neighbor + Set<NeighborSet> nsSet = computeImpactedNeighborsetForPortEvent( + newNeighborLink.dst().deviceId(), + devicePortMap.keySet()); + createGroupsFromNeighborsets(nsSet); + } + + @Override + protected void newPortToExistingNeighbor(Link newNeighborLink) { + /*log.debug("New port to existing neighbor: Updating " + + "groups for edge device {}", deviceId); + addNeighborAtPort(newNeighborLink.dst().deviceId(), + newNeighborLink.src().port()); + Set<NeighborSet> nsSet = computeImpactedNeighborsetForPortEvent( + newNeighborLink.dst().deviceId(), + devicePortMap.keySet()); + for (NeighborSet ns : nsSet) { + // Create the new bucket to be updated + TrafficTreatment.Builder tBuilder = + DefaultTrafficTreatment.builder(); + tBuilder.setOutput(newNeighborLink.src().port()) + .setEthDst(deviceConfig.getDeviceMac( + newNeighborLink.dst().deviceId())) + .setEthSrc(nodeMacAddr); + if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) { + tBuilder.pushMpls() + .setMpls(MplsLabel. + mplsLabel(ns.getEdgeLabel())); + } + + Integer nextId = deviceNextObjectiveIds.get(ns); + if (nextId != null) { + NextObjective.Builder nextObjBuilder = DefaultNextObjective + .builder().withId(nextId) + .withType(NextObjective.Type.HASHED).fromApp(appId); + + nextObjBuilder.addTreatment(tBuilder.build()); + + NextObjective nextObjective = nextObjBuilder.add(); + flowObjectiveService.next(deviceId, nextObjective); + } + }*/ + } + + @Override + protected Set<NeighborSet> computeImpactedNeighborsetForPortEvent( + DeviceId impactedNeighbor, + Set<DeviceId> updatedNeighbors) { + Set<Set<DeviceId>> powerSet = getPowerSetOfNeighbors(updatedNeighbors); + + Set<DeviceId> tmp = new HashSet<DeviceId>(); + tmp.addAll(updatedNeighbors); + tmp.remove(impactedNeighbor); + Set<Set<DeviceId>> tmpPowerSet = getPowerSetOfNeighbors(tmp); + + // Compute the impacted neighbor sets + powerSet.removeAll(tmpPowerSet); + + Set<NeighborSet> nsSet = new HashSet<NeighborSet>(); + for (Set<DeviceId> combo : powerSet) { + if (combo.isEmpty()) { + continue; + } + List<Integer> groupSegmentIds = + getSegmentIdsTobePairedWithNeighborSet(combo); + for (Integer sId : groupSegmentIds) { + NeighborSet ns = new NeighborSet(combo, sId); + log.trace("computeImpactedNeighborsetForPortEvent: sw {} " + + "combo {} sId {} ns {}", + deviceId, combo, sId, ns); + nsSet.add(ns); + } + } + log.trace("computeImpactedNeighborsetForPortEvent: The neighborset " + + "with label for sw {} is {}", + deviceId, nsSet); + return nsSet; + } + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java new file mode 100644 index 00000000..a43a0f09 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultGroupHandler.java @@ -0,0 +1,539 @@ +/* + * Copyright 2015 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.segmentrouting.grouphandler; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; + +import org.onlab.packet.MacAddress; +import org.onlab.packet.MplsLabel; +import org.onlab.util.KryoNamespace; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flowobjective.DefaultNextObjective; +import org.onosproject.net.flowobjective.FlowObjectiveService; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.Objective; +import org.onosproject.net.flowobjective.ObjectiveContext; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.group.DefaultGroupKey; +import org.onosproject.net.group.GroupKey; +import org.onosproject.net.link.LinkService; +import org.onosproject.store.service.EventuallyConsistentMap; +import org.slf4j.Logger; + +/** + * Default ECMP group handler creation module. This component creates a set of + * ECMP groups for every neighbor that this device is connected to based on + * whether the current device is an edge device or a transit device. + */ +public class DefaultGroupHandler { + protected static final Logger log = getLogger(DefaultGroupHandler.class); + + protected final DeviceId deviceId; + protected final ApplicationId appId; + protected final DeviceProperties deviceConfig; + protected final List<Integer> allSegmentIds; + protected final int nodeSegmentId; + protected final boolean isEdgeRouter; + protected final MacAddress nodeMacAddr; + protected LinkService linkService; + protected FlowObjectiveService flowObjectiveService; + + protected HashMap<DeviceId, Set<PortNumber>> devicePortMap = new HashMap<>(); + protected HashMap<PortNumber, DeviceId> portDeviceMap = new HashMap<>(); + //protected HashMap<NeighborSet, Integer> deviceNextObjectiveIds = + // new HashMap<NeighborSet, Integer>(); + protected EventuallyConsistentMap< + NeighborSetNextObjectiveStoreKey, Integer> nsNextObjStore = null; + protected Random rand = new Random(); + + protected KryoNamespace.Builder kryo = new KryoNamespace.Builder() + .register(URI.class).register(HashSet.class) + .register(DeviceId.class).register(PortNumber.class) + .register(NeighborSet.class).register(PolicyGroupIdentifier.class) + .register(PolicyGroupParams.class) + .register(GroupBucketIdentifier.class) + .register(GroupBucketIdentifier.BucketOutputType.class); + + protected DefaultGroupHandler(DeviceId deviceId, ApplicationId appId, + DeviceProperties config, + LinkService linkService, + FlowObjectiveService flowObjService, + EventuallyConsistentMap< + NeighborSetNextObjectiveStoreKey, + Integer> nsNextObjStore) { + this.deviceId = checkNotNull(deviceId); + this.appId = checkNotNull(appId); + this.deviceConfig = checkNotNull(config); + this.linkService = checkNotNull(linkService); + allSegmentIds = checkNotNull(config.getAllDeviceSegmentIds()); + nodeSegmentId = config.getSegmentId(deviceId); + isEdgeRouter = config.isEdgeDevice(deviceId); + nodeMacAddr = checkNotNull(config.getDeviceMac(deviceId)); + this.flowObjectiveService = flowObjService; + this.nsNextObjStore = nsNextObjStore; + + populateNeighborMaps(); + } + + /** + * Creates a group handler object based on the type of device. If device is + * of edge type it returns edge group handler, else it returns transit group + * handler. + * + * @param deviceId device identifier + * @param appId application identifier + * @param config interface to retrieve the device properties + * @param linkService link service object + * @param flowObjService flow objective service object + * @param nsNextObjStore next objective store map + * @return default group handler type + */ + public static DefaultGroupHandler createGroupHandler(DeviceId deviceId, + ApplicationId appId, + DeviceProperties config, + LinkService linkService, + FlowObjectiveService flowObjService, + EventuallyConsistentMap<NeighborSetNextObjectiveStoreKey, + Integer> nsNextObjStore) { + if (config.isEdgeDevice(deviceId)) { + return new DefaultEdgeGroupHandler(deviceId, appId, config, + linkService, + flowObjService, + nsNextObjStore); + } else { + return new DefaultTransitGroupHandler(deviceId, appId, config, + linkService, + flowObjService, + nsNextObjStore); + } + } + + /** + * Creates the auto created groups for this device based on the current + * snapshot of the topology. + */ + // Empty implementations to be overridden by derived classes + public void createGroups() { + } + + /** + * Performs group creation or update procedures when a new link is + * discovered on this device. + * + * @param newLink new neighbor link + */ + public void linkUp(Link newLink) { + + if (newLink.type() != Link.Type.DIRECT) { + log.warn("linkUp: unknown link type"); + return; + } + + if (!newLink.src().deviceId().equals(deviceId)) { + log.warn("linkUp: deviceId{} doesn't match with link src{}", + deviceId, newLink.src().deviceId()); + return; + } + + log.debug("Device {} linkUp at local port {} to neighbor {}", deviceId, + newLink.src().port(), newLink.dst().deviceId()); + addNeighborAtPort(newLink.dst().deviceId(), + newLink.src().port()); + /*if (devicePortMap.get(newLink.dst().deviceId()) == null) { + // New Neighbor + newNeighbor(newLink); + } else { + // Old Neighbor + newPortToExistingNeighbor(newLink); + }*/ + Set<NeighborSet> nsSet = nsNextObjStore.keySet() + .stream() + .filter((nsStoreEntry) -> (nsStoreEntry.deviceId().equals(deviceId))) + .map((nsStoreEntry) -> (nsStoreEntry.neighborSet())) + .filter((ns) -> (ns.getDeviceIds() + .contains(newLink.dst().deviceId()))) + .collect(Collectors.toSet()); + log.trace("linkUp: nsNextObjStore contents for device {}:", + deviceId, + nsSet); + for (NeighborSet ns : nsSet) { + // Create the new bucket to be updated + TrafficTreatment.Builder tBuilder = + DefaultTrafficTreatment.builder(); + tBuilder.setOutput(newLink.src().port()) + .setEthDst(deviceConfig.getDeviceMac( + newLink.dst().deviceId())) + .setEthSrc(nodeMacAddr); + if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) { + tBuilder.pushMpls() + .setMpls(MplsLabel. + mplsLabel(ns.getEdgeLabel())); + } + + Integer nextId = nsNextObjStore. + get(new NeighborSetNextObjectiveStoreKey(deviceId, ns)); + if (nextId != null) { + NextObjective.Builder nextObjBuilder = DefaultNextObjective + .builder().withId(nextId) + .withType(NextObjective.Type.HASHED).fromApp(appId); + + nextObjBuilder.addTreatment(tBuilder.build()); + + log.debug("linkUp in device {}: Adding Bucket " + + "with Port {} to next object id {}", + deviceId, + newLink.src().port(), + nextId); + NextObjective nextObjective = nextObjBuilder. + add(new SRNextObjectiveContext(deviceId)); + flowObjectiveService.next(deviceId, nextObjective); + } + } + } + + /** + * Performs group recovery procedures when a port goes down on this device. + * + * @param port port number that has gone down + */ + public void portDown(PortNumber port) { + if (portDeviceMap.get(port) == null) { + log.warn("portDown: unknown port"); + return; + } + log.debug("Device {} portDown {} to neighbor {}", deviceId, port, + portDeviceMap.get(port)); + /*Set<NeighborSet> nsSet = computeImpactedNeighborsetForPortEvent(portDeviceMap + .get(port), + devicePortMap + .keySet());*/ + Set<NeighborSet> nsSet = nsNextObjStore.keySet() + .stream() + .filter((nsStoreEntry) -> (nsStoreEntry.deviceId().equals(deviceId))) + .map((nsStoreEntry) -> (nsStoreEntry.neighborSet())) + .filter((ns) -> (ns.getDeviceIds() + .contains(portDeviceMap.get(port)))) + .collect(Collectors.toSet()); + log.trace("portDown: nsNextObjStore contents for device {}:", + deviceId, + nsSet); + for (NeighborSet ns : nsSet) { + // Create the bucket to be removed + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment + .builder(); + tBuilder.setOutput(port) + .setEthDst(deviceConfig.getDeviceMac(portDeviceMap + .get(port))).setEthSrc(nodeMacAddr); + if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) { + tBuilder.pushMpls().setMpls(MplsLabel.mplsLabel(ns + .getEdgeLabel())); + } + + Integer nextId = nsNextObjStore. + get(new NeighborSetNextObjectiveStoreKey(deviceId, ns)); + if (nextId != null) { + NextObjective.Builder nextObjBuilder = DefaultNextObjective + .builder().withType(NextObjective.Type.SIMPLE).withId(nextId).fromApp(appId); + + nextObjBuilder.addTreatment(tBuilder.build()); + + log.debug("portDown in device {}: Removing Bucket " + + "with Port {} to next object id {}", + deviceId, + port, + nextId); + NextObjective nextObjective = nextObjBuilder. + remove(new SRNextObjectiveContext(deviceId)); + + flowObjectiveService.next(deviceId, nextObjective); + } + + } + + devicePortMap.get(portDeviceMap.get(port)).remove(port); + portDeviceMap.remove(port); + } + + /** + * Returns the next objective associated with the neighborset. + * If there is no next objective for this neighborset, this API + * would create a next objective and return. + * + * @param ns neighborset + * @return int if found or -1 + */ + public int getNextObjectiveId(NeighborSet ns) { + Integer nextId = nsNextObjStore. + get(new NeighborSetNextObjectiveStoreKey(deviceId, ns)); + if (nextId == null) { + log.trace("getNextObjectiveId in device{}: Next objective id " + + "not found for {} and creating", deviceId, ns); + log.trace("getNextObjectiveId: nsNextObjStore contents for device {}: {}", + deviceId, + nsNextObjStore.entrySet() + .stream() + .filter((nsStoreEntry) -> + (nsStoreEntry.getKey().deviceId().equals(deviceId))) + .collect(Collectors.toList())); + createGroupsFromNeighborsets(Collections.singleton(ns)); + nextId = nsNextObjStore. + get(new NeighborSetNextObjectiveStoreKey(deviceId, ns)); + if (nextId == null) { + log.warn("getNextObjectiveId: unable to create next objective"); + return -1; + } else { + log.debug("getNextObjectiveId in device{}: Next objective id {} " + + "created for {}", deviceId, nextId.intValue(), ns); + } + } else { + log.trace("getNextObjectiveId in device{}: Next objective id {} " + + "found for {}", deviceId, nextId.intValue(), ns); + } + return nextId.intValue(); + } + + /** + * Checks if the next objective ID (group) for the neighbor set exists or not. + * + * @param ns neighbor set to check + * @return true if it exists, false otherwise + */ + public boolean hasNextObjectiveId(NeighborSet ns) { + Integer nextId = nsNextObjStore. + get(new NeighborSetNextObjectiveStoreKey(deviceId, ns)); + if (nextId == null) { + return false; + } + + return true; + } + + // Empty implementation + protected void newNeighbor(Link newLink) { + } + + // Empty implementation + protected void newPortToExistingNeighbor(Link newLink) { + } + + // Empty implementation + protected Set<NeighborSet> + computeImpactedNeighborsetForPortEvent(DeviceId impactedNeighbor, + Set<DeviceId> updatedNeighbors) { + return null; + } + + private void populateNeighborMaps() { + Set<Link> outgoingLinks = linkService.getDeviceEgressLinks(deviceId); + for (Link link : outgoingLinks) { + if (link.type() != Link.Type.DIRECT) { + continue; + } + addNeighborAtPort(link.dst().deviceId(), link.src().port()); + } + } + + protected void addNeighborAtPort(DeviceId neighborId, + PortNumber portToNeighbor) { + // Update DeviceToPort database + log.debug("Device {} addNeighborAtPort: neighbor {} at port {}", + deviceId, neighborId, portToNeighbor); + if (devicePortMap.get(neighborId) != null) { + devicePortMap.get(neighborId).add(portToNeighbor); + } else { + Set<PortNumber> ports = new HashSet<PortNumber>(); + ports.add(portToNeighbor); + devicePortMap.put(neighborId, ports); + } + + // Update portToDevice database + if (portDeviceMap.get(portToNeighbor) == null) { + portDeviceMap.put(portToNeighbor, neighborId); + } + } + + protected Set<Set<DeviceId>> getPowerSetOfNeighbors(Set<DeviceId> neighbors) { + List<DeviceId> list = new ArrayList<DeviceId>(neighbors); + Set<Set<DeviceId>> sets = new HashSet<Set<DeviceId>>(); + // get the number of elements in the neighbors + int elements = list.size(); + // the number of members of a power set is 2^n + // including the empty set + int powerElements = (1 << elements); + + // run a binary counter for the number of power elements + // NOTE: Exclude empty set + for (long i = 1; i < powerElements; i++) { + Set<DeviceId> neighborSubSet = new HashSet<DeviceId>(); + for (int j = 0; j < elements; j++) { + if ((i >> j) % 2 == 1) { + neighborSubSet.add(list.get(j)); + } + } + sets.add(neighborSubSet); + } + return sets; + } + + private boolean isSegmentIdSameAsNodeSegmentId(DeviceId deviceId, int sId) { + return (deviceConfig.getSegmentId(deviceId) == sId); + } + + protected List<Integer> getSegmentIdsTobePairedWithNeighborSet(Set<DeviceId> neighbors) { + + List<Integer> nsSegmentIds = new ArrayList<Integer>(); + + // Always pair up with no edge label + // If (neighbors.size() == 1) { + nsSegmentIds.add(-1); + // } + + // Filter out SegmentIds matching with the + // nodes in the combo + for (Integer sId : allSegmentIds) { + if (sId.equals(nodeSegmentId)) { + continue; + } + boolean filterOut = false; + // Check if the edge label being set is of + // any node in the Neighbor set + for (DeviceId deviceId : neighbors) { + if (isSegmentIdSameAsNodeSegmentId(deviceId, sId)) { + filterOut = true; + break; + } + } + if (!filterOut) { + nsSegmentIds.add(sId); + } + } + return nsSegmentIds; + } + + /** + * Creates Groups from a set of NeighborSet given. + * + * @param nsSet a set of NeighborSet + */ + public void createGroupsFromNeighborsets(Set<NeighborSet> nsSet) { + for (NeighborSet ns : nsSet) { + int nextId = flowObjectiveService.allocateNextId(); + NextObjective.Builder nextObjBuilder = DefaultNextObjective + .builder().withId(nextId) + .withType(NextObjective.Type.HASHED).fromApp(appId); + for (DeviceId d : ns.getDeviceIds()) { + if (devicePortMap.get(d) == null) { + log.warn("Device {} is not in the port map yet", d); + return; + } else if (devicePortMap.get(d).size() == 0) { + log.warn("There are no ports for " + + "the Device {} in the port map yet", d); + return; + } + + for (PortNumber sp : devicePortMap.get(d)) { + TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment + .builder(); + tBuilder.setOutput(sp) + .setEthDst(deviceConfig.getDeviceMac(d)) + .setEthSrc(nodeMacAddr); + if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) { + tBuilder.pushMpls().setMpls(MplsLabel.mplsLabel(ns + .getEdgeLabel())); + } + nextObjBuilder.addTreatment(tBuilder.build()); + } + } + + NextObjective nextObj = nextObjBuilder. + add(new SRNextObjectiveContext(deviceId)); + flowObjectiveService.next(deviceId, nextObj); + log.debug("createGroupsFromNeighborsets: Submited " + + "next objective {} in device {}", + nextId, deviceId); + nsNextObjStore.put(new NeighborSetNextObjectiveStoreKey(deviceId, ns), + nextId); + } + } + + public GroupKey getGroupKey(Object obj) { + return new DefaultGroupKey(kryo.build().serialize(obj)); + } + + /** + * Removes groups for the next objective ID given. + * + * @param objectiveId next objective ID to remove + * @return true if succeeds, false otherwise + */ + public boolean removeGroup(int objectiveId) { + + if (nsNextObjStore.containsValue(objectiveId)) { + NextObjective.Builder nextObjBuilder = DefaultNextObjective + .builder().withId(objectiveId) + .withType(NextObjective.Type.HASHED).fromApp(appId); + NextObjective nextObjective = nextObjBuilder. + remove(new SRNextObjectiveContext(deviceId)); + flowObjectiveService.next(deviceId, nextObjective); + + for (Map.Entry<NeighborSetNextObjectiveStoreKey, Integer> entry: nsNextObjStore.entrySet()) { + if (entry.getValue().equals(objectiveId)) { + nsNextObjStore.remove(entry.getKey()); + break; + } + } + return true; + } + + return false; + } + + protected static class SRNextObjectiveContext implements ObjectiveContext { + final DeviceId deviceId; + + SRNextObjectiveContext(DeviceId deviceId) { + this.deviceId = deviceId; + } + @Override + public void onSuccess(Objective objective) { + log.debug("Next objective operation successful in device {}", + deviceId); + } + + @Override + public void onError(Objective objective, ObjectiveError error) { + log.warn("Next objective {} operation failed with error: {} in device {}", + objective, error, deviceId); + } + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultTransitGroupHandler.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultTransitGroupHandler.java new file mode 100644 index 00000000..e12426c2 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DefaultTransitGroupHandler.java @@ -0,0 +1,185 @@ +/* + * Copyright 2015 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.segmentrouting.grouphandler; + +import java.util.HashSet; +import java.util.Set; + +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link; +import org.onosproject.net.flowobjective.FlowObjectiveService; +import org.onosproject.net.link.LinkService; +import org.onosproject.store.service.EventuallyConsistentMap; + +/** + * Default ECMP group handler creation module for a transit device. + * This component creates a set of ECMP groups for every neighbor + * that this device is connected to. + * For example, consider a network of 4 devices: D0 (Segment ID: 100), + * D1 (Segment ID: 101), D2 (Segment ID: 102) and D3 (Segment ID: 103), + * where D0 and D3 are edge devices and D1 and D2 are transit devices. + * Assume transit device D1 is connected to 2 neighbors (D0 and D3 ). + * The following groups will be created in D1: + * 1) all ports to D0 + with no label push, + * 2) all ports to D3 + with no label push, + */ +public class DefaultTransitGroupHandler extends DefaultGroupHandler { + + protected DefaultTransitGroupHandler(DeviceId deviceId, + ApplicationId appId, + DeviceProperties config, + LinkService linkService, + FlowObjectiveService flowObjService, + EventuallyConsistentMap< + NeighborSetNextObjectiveStoreKey, + Integer> nsNextObjStore) { + super(deviceId, appId, config, linkService, flowObjService, nsNextObjStore); + } + + @Override + public void createGroups() { + Set<DeviceId> neighbors = devicePortMap.keySet(); + if (neighbors == null || neighbors.isEmpty()) { + return; + } + + // Create all possible Neighbor sets from this router + // NOTE: Avoid any pairings of edge routers only + Set<Set<DeviceId>> sets = getPowerSetOfNeighbors(neighbors); + sets = filterEdgeRouterOnlyPairings(sets); + log.debug("createGroupsAtTransitRouter: The size of neighbor powerset " + + "for sw {} is {}", deviceId, sets.size()); + Set<NeighborSet> nsSet = new HashSet<NeighborSet>(); + for (Set<DeviceId> combo : sets) { + if (combo.isEmpty()) { + continue; + } + NeighborSet ns = new NeighborSet(combo); + log.debug("createGroupsAtTransitRouter: sw {} combo {} ns {}", + deviceId, combo, ns); + nsSet.add(ns); + } + log.debug("createGroupsAtTransitRouter: The neighborset with label " + + "for sw {} is {}", deviceId, nsSet); + + createGroupsFromNeighborsets(nsSet); + } + + @Override + protected void newNeighbor(Link newNeighborLink) { + log.debug("New Neighbor: Updating groups for " + + "transit device {}", deviceId); + // Recompute neighbor power set + addNeighborAtPort(newNeighborLink.dst().deviceId(), + newNeighborLink.src().port()); + // Compute new neighbor sets due to the addition of new neighbor + Set<NeighborSet> nsSet = computeImpactedNeighborsetForPortEvent( + newNeighborLink.dst().deviceId(), + devicePortMap.keySet()); + createGroupsFromNeighborsets(nsSet); + } + + @Override + protected void newPortToExistingNeighbor(Link newNeighborLink) { + /*log.debug("New port to existing neighbor: Updating " + + "groups for transit device {}", deviceId); + addNeighborAtPort(newNeighborLink.dst().deviceId(), + newNeighborLink.src().port()); + Set<NeighborSet> nsSet = computeImpactedNeighborsetForPortEvent( + newNeighborLink.dst().deviceId(), + devicePortMap.keySet()); + for (NeighborSet ns : nsSet) { + // Create the new bucket to be updated + TrafficTreatment.Builder tBuilder = + DefaultTrafficTreatment.builder(); + tBuilder.setOutput(newNeighborLink.src().port()) + .setEthDst(deviceConfig.getDeviceMac( + newNeighborLink.dst().deviceId())) + .setEthSrc(nodeMacAddr); + if (ns.getEdgeLabel() != NeighborSet.NO_EDGE_LABEL) { + tBuilder.pushMpls() + .setMpls(MplsLabel. + mplsLabel(ns.getEdgeLabel())); + } + + + Integer nextId = deviceNextObjectiveIds.get(ns); + if (nextId != null) { + NextObjective.Builder nextObjBuilder = DefaultNextObjective + .builder().withId(nextId) + .withType(NextObjective.Type.HASHED).fromApp(appId); + + nextObjBuilder.addTreatment(tBuilder.build()); + + NextObjective nextObjective = nextObjBuilder.add(); + flowObjectiveService.next(deviceId, nextObjective); + } + }*/ + } + + @Override + protected Set<NeighborSet> computeImpactedNeighborsetForPortEvent( + DeviceId impactedNeighbor, + Set<DeviceId> updatedNeighbors) { + Set<Set<DeviceId>> powerSet = getPowerSetOfNeighbors(updatedNeighbors); + + Set<DeviceId> tmp = new HashSet<DeviceId>(); + tmp.addAll(updatedNeighbors); + tmp.remove(impactedNeighbor); + Set<Set<DeviceId>> tmpPowerSet = getPowerSetOfNeighbors(tmp); + + // Compute the impacted neighbor sets + powerSet.removeAll(tmpPowerSet); + + powerSet = filterEdgeRouterOnlyPairings(powerSet); + Set<NeighborSet> nsSet = new HashSet<NeighborSet>(); + for (Set<DeviceId> combo : powerSet) { + if (combo.isEmpty()) { + continue; + } + NeighborSet ns = new NeighborSet(combo); + log.debug("createGroupsAtTransitRouter: sw {} combo {} ns {}", + deviceId, combo, ns); + nsSet.add(ns); + } + log.debug("computeImpactedNeighborsetForPortEvent: The neighborset with label " + + "for sw {} is {}", deviceId, nsSet); + + return nsSet; + } + + private Set<Set<DeviceId>> filterEdgeRouterOnlyPairings(Set<Set<DeviceId>> sets) { + Set<Set<DeviceId>> fiteredSets = new HashSet<Set<DeviceId>>(); + for (Set<DeviceId> deviceSubSet : sets) { + if (deviceSubSet.size() > 1) { + boolean avoidEdgeRouterPairing = true; + for (DeviceId device : deviceSubSet) { + if (!deviceConfig.isEdgeDevice(device)) { + avoidEdgeRouterPairing = false; + break; + } + } + if (!avoidEdgeRouterPairing) { + fiteredSets.add(deviceSubSet); + } + } else { + fiteredSets.add(deviceSubSet); + } + } + return fiteredSets; + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DeviceProperties.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DeviceProperties.java new file mode 100644 index 00000000..816ca7bc --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/DeviceProperties.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 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.segmentrouting.grouphandler; + +import java.util.List; + +import org.onlab.packet.MacAddress; +import org.onosproject.net.DeviceId; + +/** + * Mechanism through which group handler module retrieves + * the device specific attributes such as segment ID, + * Mac address...etc from group handler applications. + */ +public interface DeviceProperties { + /** + * Returns the segment id of a device to be used in group creation. + * + * @param deviceId device identifier + * @return segment id of a device + */ + int getSegmentId(DeviceId deviceId); + /** + * Returns the Mac address of a device to be used in group creation. + * + * @param deviceId device identifier + * @return mac address of a device + */ + MacAddress getDeviceMac(DeviceId deviceId); + /** + * Indicates whether a device is edge device or transit/core device. + * + * @param deviceId device identifier + * @return boolean + */ + boolean isEdgeDevice(DeviceId deviceId); + /** + * Returns all segment IDs to be considered in building auto + * + * created groups. + * @return list of segment IDs + */ + List<Integer> getAllDeviceSegmentIds(); +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/GroupBucketIdentifier.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/GroupBucketIdentifier.java new file mode 100644 index 00000000..2ecb4493 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/GroupBucketIdentifier.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015 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.segmentrouting.grouphandler; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.onosproject.net.PortNumber; + +/** + * Representation of policy group bucket identifier. Not exposed to + * the application and only to be used internally. + */ +public class GroupBucketIdentifier { + private int label; + private BucketOutputType type; + private PortNumber outPort; + private PolicyGroupIdentifier outGroup; + + protected enum BucketOutputType { + PORT, + GROUP + } + + protected GroupBucketIdentifier(int label, + PortNumber outPort) { + this.label = label; + this.type = BucketOutputType.PORT; + this.outPort = checkNotNull(outPort); + this.outGroup = null; + } + + protected GroupBucketIdentifier(int label, + PolicyGroupIdentifier outGroup) { + this.label = label; + this.type = BucketOutputType.GROUP; + this.outPort = null; + this.outGroup = checkNotNull(outGroup); + } + + protected int label() { + return this.label; + } + + protected BucketOutputType type() { + return this.type; + } + + protected PortNumber outPort() { + return this.outPort; + } + + protected PolicyGroupIdentifier outGroup() { + return this.outGroup; + } +} + diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/NeighborSet.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/NeighborSet.java new file mode 100644 index 00000000..7ada3224 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/NeighborSet.java @@ -0,0 +1,123 @@ +/* + * Copyright 2015 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.segmentrouting.grouphandler; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.onosproject.net.DeviceId; + +/** + * Representation of a set of neighbor switch dpids along with edge node + * label. Meant to be used as a lookup-key in a hash-map to retrieve an + * ECMP-group that hashes packets to a set of ports connecting to the + * neighbors in this set. + */ +public class NeighborSet { + private final Set<DeviceId> neighbors; + private final int edgeLabel; + public static final int NO_EDGE_LABEL = -1; + + /** + * Constructor with set of neighbors. Edge label is + * default to -1. + * + * @param neighbors set of neighbors to be part of neighbor set + */ + public NeighborSet(Set<DeviceId> neighbors) { + checkNotNull(neighbors); + this.edgeLabel = NO_EDGE_LABEL; + this.neighbors = new HashSet<DeviceId>(); + this.neighbors.addAll(neighbors); + } + + /** + * Constructor with set of neighbors and edge label. + * + * @param neighbors set of neighbors to be part of neighbor set + * @param edgeLabel label to be pushed as part of group operation + */ + public NeighborSet(Set<DeviceId> neighbors, int edgeLabel) { + checkNotNull(neighbors); + this.edgeLabel = edgeLabel; + this.neighbors = new HashSet<DeviceId>(); + this.neighbors.addAll(neighbors); + } + + /** + * Default constructor for kryo serialization. + */ + public NeighborSet() { + this.edgeLabel = NO_EDGE_LABEL; + this.neighbors = new HashSet<DeviceId>(); + } + + /** + * Gets the neighbors part of neighbor set. + * + * @return set of neighbor identifiers + */ + public Set<DeviceId> getDeviceIds() { + return neighbors; + } + + /** + * Gets the label associated with neighbor set. + * + * @return integer + */ + public int getEdgeLabel() { + return edgeLabel; + } + + // The list of neighbor ids and label are used for comparison. + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof NeighborSet)) { + return false; + } + NeighborSet that = (NeighborSet) o; + return (this.neighbors.containsAll(that.neighbors) && + that.neighbors.containsAll(this.neighbors) && + (this.edgeLabel == that.edgeLabel)); + } + + // The list of neighbor ids and label are used for comparison. + @Override + public int hashCode() { + int result = 17; + int combinedHash = 0; + for (DeviceId d : neighbors) { + combinedHash = combinedHash + Objects.hash(d); + } + result = 31 * result + combinedHash + Objects.hash(edgeLabel); + + return result; + } + + @Override + public String toString() { + return " Neighborset Sw: " + neighbors + + " and Label: " + edgeLabel; + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/NeighborSetNextObjectiveStoreKey.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/NeighborSetNextObjectiveStoreKey.java new file mode 100644 index 00000000..9ace5313 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/NeighborSetNextObjectiveStoreKey.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015 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.segmentrouting.grouphandler; + +import java.util.Objects; + +import org.onosproject.net.DeviceId; + +/** + * Class definition of Key for Neighborset to NextObjective store. + */ +public class NeighborSetNextObjectiveStoreKey { + private final DeviceId deviceId; + private final NeighborSet ns; + + public NeighborSetNextObjectiveStoreKey(DeviceId deviceId, + NeighborSet ns) { + this.deviceId = deviceId; + this.ns = ns; + } + + public DeviceId deviceId() { + return this.deviceId; + } + + public NeighborSet neighborSet() { + return this.ns; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof NeighborSetNextObjectiveStoreKey)) { + return false; + } + NeighborSetNextObjectiveStoreKey that = + (NeighborSetNextObjectiveStoreKey) o; + return (Objects.equals(this.deviceId, that.deviceId) && + Objects.equals(this.ns, that.ns)); + } + + // The list of neighbor ids and label are used for comparison. + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Objects.hashCode(this.deviceId) + + Objects.hashCode(this.ns); + + return result; + } + + @Override + public String toString() { + return "Device: " + deviceId + " Neighborset: " + ns; + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupHandler.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupHandler.java new file mode 100644 index 00000000..e7e87839 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupHandler.java @@ -0,0 +1,352 @@ +/* + * Copyright 2015 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.segmentrouting.grouphandler; + +import static com.google.common.base.Preconditions.checkArgument; +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import org.onlab.packet.MplsLabel; +import org.onosproject.core.ApplicationId; +import org.onosproject.segmentrouting.grouphandler.GroupBucketIdentifier.BucketOutputType; +import org.onosproject.store.service.EventuallyConsistentMap; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flowobjective.FlowObjectiveService; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.link.LinkService; +import org.slf4j.Logger; + +/** + * A module to create group chains based on the specified device + * ports and label stack to be applied on each port. + */ +public class PolicyGroupHandler extends DefaultGroupHandler { + + private final Logger log = getLogger(getClass()); + private HashMap<PolicyGroupIdentifier, PolicyGroupIdentifier> dependentGroups = new HashMap<>(); + + /** + * Policy group handler constructor. + * + * @param deviceId device identifier + * @param appId application identifier + * @param config interface to retrieve the device properties + * @param linkService link service object + * @param flowObjService flow objective service object + * @param nsNextObjStore next objective store map + */ + public PolicyGroupHandler(DeviceId deviceId, + ApplicationId appId, + DeviceProperties config, + LinkService linkService, + FlowObjectiveService flowObjService, + EventuallyConsistentMap<NeighborSetNextObjectiveStoreKey, + Integer> nsNextObjStore) { + super(deviceId, appId, config, linkService, flowObjService, nsNextObjStore); + } + + public PolicyGroupIdentifier createPolicyGroupChain(String id, + List<PolicyGroupParams> params) { + List<GroupBucketIdentifier> bucketIds = new ArrayList<>(); + for (PolicyGroupParams param: params) { + List<PortNumber> ports = param.getPorts(); + if (ports == null) { + log.warn("createPolicyGroupChain in sw {} with wrong " + + "input parameters", deviceId); + return null; + } + + int labelStackSize = (param.getLabelStack() != null) ? + param.getLabelStack().size() : 0; + + if (labelStackSize > 1) { + for (PortNumber sp : ports) { + PolicyGroupIdentifier previousGroupkey = null; + DeviceId neighbor = portDeviceMap.get(sp); + for (int idx = 0; idx < param.getLabelStack().size(); idx++) { + int label = param.getLabelStack().get(idx); + if (idx == (labelStackSize - 1)) { + // Innermost Group + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, + previousGroupkey); + bucketIds.add(bucketId); + } else if (idx == 0) { + // Outermost Group + List<GroupBucket> outBuckets = new ArrayList<>(); + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, sp); + PolicyGroupIdentifier key = new + PolicyGroupIdentifier(id, + Collections.singletonList(param), + Collections.singletonList(bucketId)); + TrafficTreatment.Builder tBuilder = + DefaultTrafficTreatment.builder(); + tBuilder.setOutput(sp) + .setEthDst(deviceConfig. + getDeviceMac(neighbor)) + .setEthSrc(nodeMacAddr) + .pushMpls() + .setMpls(MplsLabel.mplsLabel(label)); + /*outBuckets.add(DefaultGroupBucket. + createSelectGroupBucket(tBuilder.build())); + GroupDescription desc = new + DefaultGroupDescription(deviceId, + GroupDescription.Type.INDIRECT, + new GroupBuckets(outBuckets)); + //TODO: BoS*/ + previousGroupkey = key; + //groupService.addGroup(desc); + //TODO: Use nextObjective APIs here + } else { + // Intermediate Groups + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, + previousGroupkey); + PolicyGroupIdentifier key = new + PolicyGroupIdentifier(id, + Collections.singletonList(param), + Collections.singletonList(bucketId)); + // Add to group dependency list + dependentGroups.put(previousGroupkey, key); + previousGroupkey = key; + } + } + } + } else { + int label = -1; + if (labelStackSize == 1) { + label = param.getLabelStack().get(0); + } + for (PortNumber sp : ports) { + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, sp); + bucketIds.add(bucketId); + } + } + } + PolicyGroupIdentifier innermostGroupkey = null; + if (!bucketIds.isEmpty()) { + innermostGroupkey = new + PolicyGroupIdentifier(id, + params, + bucketIds); + // Add to group dependency list + boolean fullyResolved = true; + for (GroupBucketIdentifier bucketId:bucketIds) { + if (bucketId.type() == BucketOutputType.GROUP) { + dependentGroups.put(bucketId.outGroup(), + innermostGroupkey); + fullyResolved = false; + } + } + + if (fullyResolved) { + List<GroupBucket> outBuckets = new ArrayList<>(); + for (GroupBucketIdentifier bucketId:bucketIds) { + DeviceId neighbor = portDeviceMap. + get(bucketId.outPort()); + TrafficTreatment.Builder tBuilder = + DefaultTrafficTreatment.builder(); + tBuilder.setOutput(bucketId.outPort()) + .setEthDst(deviceConfig. + getDeviceMac(neighbor)) + .setEthSrc(nodeMacAddr); + if (bucketId.label() != NeighborSet.NO_EDGE_LABEL) { + tBuilder.pushMpls() + .setMpls(MplsLabel.mplsLabel(bucketId.label())); + } + //TODO: BoS + /*outBuckets.add(DefaultGroupBucket. + createSelectGroupBucket(tBuilder.build()));*/ + } + /*GroupDescription desc = new + DefaultGroupDescription(deviceId, + GroupDescription.Type.SELECT, + new GroupBuckets(outBuckets)); + groupService.addGroup(desc);*/ + //TODO: Use nextObjective APIs here + } + } + return innermostGroupkey; + } + + //TODO: Use nextObjective APIs to handle the group chains + /*@Override + protected void handleGroupEvent(GroupEvent event) { + if (event.type() == GroupEvent.Type.GROUP_ADDED) { + if (dependentGroups.get(event.subject().appCookie()) != null) { + PolicyGroupIdentifier dependentGroupKey = dependentGroups.get(event.subject().appCookie()); + dependentGroups.remove(event.subject().appCookie()); + boolean fullyResolved = true; + for (GroupBucketIdentifier bucketId: + dependentGroupKey.bucketIds()) { + if (bucketId.type() != BucketOutputType.GROUP) { + continue; + } + if (dependentGroups.containsKey(bucketId.outGroup())) { + fullyResolved = false; + break; + } + } + + if (fullyResolved) { + List<GroupBucket> outBuckets = new ArrayList<GroupBucket>(); + for (GroupBucketIdentifier bucketId: + dependentGroupKey.bucketIds()) { + TrafficTreatment.Builder tBuilder = + DefaultTrafficTreatment.builder(); + if (bucketId.label() != NeighborSet.NO_EDGE_LABEL) { + tBuilder.pushMpls() + .setMpls(MplsLabel. + mplsLabel(bucketId.label())); + } + //TODO: BoS + if (bucketId.type() == BucketOutputType.PORT) { + DeviceId neighbor = portDeviceMap. + get(bucketId.outPort()); + tBuilder.setOutput(bucketId.outPort()) + .setEthDst(deviceConfig. + getDeviceMac(neighbor)) + .setEthSrc(nodeMacAddr); + } else { + if (groupService. + getGroup(deviceId, + getGroupKey(bucketId. + outGroup())) == null) { + throw new IllegalStateException(); + } + GroupId indirectGroupId = groupService. + getGroup(deviceId, + getGroupKey(bucketId. + outGroup())).id(); + tBuilder.group(indirectGroupId); + } + outBuckets.add(DefaultGroupBucket. + createSelectGroupBucket(tBuilder.build())); + } + GroupDescription desc = new + DefaultGroupDescription(deviceId, + GroupDescription.Type.SELECT, + new GroupBuckets(outBuckets)); + groupService.addGroup(desc); + } + } + } + }*/ + + public PolicyGroupIdentifier generatePolicyGroupKey(String id, + List<PolicyGroupParams> params) { + List<GroupBucketIdentifier> bucketIds = new ArrayList<>(); + for (PolicyGroupParams param: params) { + List<PortNumber> ports = param.getPorts(); + if (ports == null) { + log.warn("generateGroupKey in sw {} with wrong " + + "input parameters", deviceId); + return null; + } + + int labelStackSize = (param.getLabelStack() != null) + ? param.getLabelStack().size() : 0; + + if (labelStackSize > 1) { + for (PortNumber sp : ports) { + PolicyGroupIdentifier previousGroupkey = null; + for (int idx = 0; idx < param.getLabelStack().size(); idx++) { + int label = param.getLabelStack().get(idx); + if (idx == (labelStackSize - 1)) { + // Innermost Group + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, + previousGroupkey); + bucketIds.add(bucketId); + } else if (idx == 0) { + // Outermost Group + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, sp); + PolicyGroupIdentifier key = new + PolicyGroupIdentifier(id, + Collections.singletonList(param), + Collections.singletonList(bucketId)); + previousGroupkey = key; + } else { + // Intermediate Groups + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, + previousGroupkey); + PolicyGroupIdentifier key = new + PolicyGroupIdentifier(id, + Collections.singletonList(param), + Collections.singletonList(bucketId)); + previousGroupkey = key; + } + } + } + } else { + int label = -1; + if (labelStackSize == 1) { + label = param.getLabelStack().get(0); + } + for (PortNumber sp : ports) { + GroupBucketIdentifier bucketId = + new GroupBucketIdentifier(label, sp); + bucketIds.add(bucketId); + } + } + } + PolicyGroupIdentifier innermostGroupkey = null; + if (!bucketIds.isEmpty()) { + innermostGroupkey = new + PolicyGroupIdentifier(id, + params, + bucketIds); + } + return innermostGroupkey; + } + + public void removeGroupChain(PolicyGroupIdentifier key) { + checkArgument(key != null); + List<PolicyGroupIdentifier> groupsToBeDeleted = new ArrayList<>(); + groupsToBeDeleted.add(key); + + Iterator<PolicyGroupIdentifier> it = + groupsToBeDeleted.iterator(); + + while (it.hasNext()) { + PolicyGroupIdentifier innerMostGroupKey = it.next(); + for (GroupBucketIdentifier bucketId: + innerMostGroupKey.bucketIds()) { + if (bucketId.type() != BucketOutputType.GROUP) { + groupsToBeDeleted.add(bucketId.outGroup()); + } + } + /*groupService.removeGroup(deviceId, + getGroupKey(innerMostGroupKey), + appId);*/ + //TODO: Use nextObjective APIs here + it.remove(); + } + } + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupIdentifier.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupIdentifier.java new file mode 100644 index 00000000..44a0a2ce --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupIdentifier.java @@ -0,0 +1,90 @@ +/* + * Copyright 2015 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.segmentrouting.grouphandler; + +import java.util.List; + +/** + * Representation of policy based group identifiers. + * Opaque to group handler applications and only the outermost + * policy group identifier in a chain is visible to the applications. + */ +public class PolicyGroupIdentifier { + private String id; + private List<PolicyGroupParams> inputParams; + private List<GroupBucketIdentifier> bucketIds; + + /** + * Constructor. + * + * @param id unique identifier associated with the policy group + * @param input policy group params associated with this group + * @param bucketIds buckets associated with this group + */ + protected PolicyGroupIdentifier(String id, + List<PolicyGroupParams> input, + List<GroupBucketIdentifier> bucketIds) { + this.id = id; + this.inputParams = input; + this.bucketIds = bucketIds; + } + + /** + * Returns the bucket identifier list associated with the policy + * group identifier. + * + * @return list of bucket identifier + */ + protected List<GroupBucketIdentifier> bucketIds() { + return this.bucketIds; + } + + @Override + public int hashCode() { + int result = 17; + int combinedHash = 0; + for (PolicyGroupParams input:inputParams) { + combinedHash = combinedHash + input.hashCode(); + } + for (GroupBucketIdentifier bucketId:bucketIds) { + combinedHash = combinedHash + bucketId.hashCode(); + } + result = 31 * result + combinedHash; + + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof PolicyGroupIdentifier) { + PolicyGroupIdentifier that = (PolicyGroupIdentifier) obj; + boolean result = this.id.equals(that.id); + result = result && + this.inputParams.containsAll(that.inputParams) && + that.inputParams.containsAll(this.inputParams); + result = result && + this.bucketIds.containsAll(that.bucketIds) && + that.bucketIds.containsAll(this.bucketIds); + return result; + } + + return false; + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupParams.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupParams.java new file mode 100644 index 00000000..5a3f26b5 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/PolicyGroupParams.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015 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.segmentrouting.grouphandler; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.List; +import java.util.Objects; + +import org.onosproject.net.PortNumber; + +/** + * Representation of parameters used to create policy based groups. + */ +public class PolicyGroupParams { + private final List<PortNumber> ports; + private final List<Integer> labelStack; + + /** + * Constructor. + * + * @param labelStack mpls label stack to be applied on the ports + * @param ports ports to be part of the policy group + */ + public PolicyGroupParams(List<Integer> labelStack, + List<PortNumber> ports) { + this.ports = checkNotNull(ports); + this.labelStack = checkNotNull(labelStack); + } + + /** + * Returns the ports associated with the policy group params. + * + * @return list of port numbers + */ + public List<PortNumber> getPorts() { + return ports; + } + + /** + * Returns the label stack associated with the policy group params. + * + * @return list of integers + */ + public List<Integer> getLabelStack() { + return labelStack; + } + + @Override + public int hashCode() { + int result = 17; + int combinedHash = 0; + for (PortNumber port:ports) { + combinedHash = combinedHash + port.hashCode(); + } + combinedHash = combinedHash + Objects.hash(labelStack); + result = 31 * result + combinedHash; + + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof PolicyGroupParams) { + PolicyGroupParams that = (PolicyGroupParams) obj; + boolean result = this.labelStack.equals(that.labelStack); + result = result && + this.ports.containsAll(that.ports) && + that.ports.containsAll(this.ports); + return result; + } + + return false; + } +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/package-info.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/package-info.java new file mode 100644 index 00000000..1a8d595e --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/grouphandler/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Segment routing group handling. + */ +package org.onosproject.segmentrouting.grouphandler;
\ No newline at end of file diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/package-info.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/package-info.java new file mode 100644 index 00000000..7b81db8a --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Segment routing application components. + */ +package org.onosproject.segmentrouting;
\ No newline at end of file diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/PolicyCodec.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/PolicyCodec.java new file mode 100644 index 00000000..8e508872 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/PolicyCodec.java @@ -0,0 +1,122 @@ +/* + * Copyright 2015 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.segmentrouting.web; + +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.onosproject.segmentrouting.Policy; +import org.onosproject.segmentrouting.TunnelPolicy; + +public final class PolicyCodec extends JsonCodec<Policy> { + + // JSON field names + private static final String POLICY_ID = "policy_id"; + private static final String PRIORITY = "priority"; + private static final String TYPE = "policy_type"; + private static final String TUNNEL_ID = "tunnel_id"; + private static final String DST_IP = "dst_ip"; + private static final String SRC_IP = "src_ip"; + private static final String PROTO_TYPE = "proto_type"; + private static final String SRC_PORT = "src_tp_port"; + private static final String DST_PORT = "dst_tp_port"; + + @Override + public ObjectNode encode(Policy policy, CodecContext context) { + final ObjectNode result = context.mapper().createObjectNode() + .put(POLICY_ID, policy.id()); + + result.put(PRIORITY, policy.priority()); + result.put(TYPE, policy.type().toString()); + + if (policy.dstIp() != null) { + result.put(DST_IP, policy.dstIp()); + } + if (policy.srcIp() != null) { + result.put(SRC_IP, policy.srcIp()); + } + if (policy.ipProto() != null) { + result.put(PROTO_TYPE, policy.ipProto()); + } + + int srcPort = policy.srcPort() & 0xffff; + if (policy.srcPort() != 0) { + result.put(SRC_PORT, srcPort); + } + int dstPort = policy.dstPort() & 0xffff; + if (policy.dstPort() != 0) { + result.put(DST_PORT, dstPort); + } + if (policy.type() == Policy.Type.TUNNEL_FLOW) { + result.put(TUNNEL_ID, ((TunnelPolicy) policy).tunnelId()); + } + + return result; + } + + @Override + public Policy decode(ObjectNode json, CodecContext context) { + + String pid = json.path(POLICY_ID).asText(); + String type = json.path(TYPE).asText(); + int priority = json.path(PRIORITY).asInt(); + String dstIp = json.path(DST_IP).asText(); + String srcIp = json.path(SRC_IP).asText(); + String tunnelId = json.path(TUNNEL_ID).asText(); + String protoType = json.path(PROTO_TYPE).asText(); + short srcPort = json.path(SRC_PORT).shortValue(); + short dstPort = json.path(DST_PORT).shortValue(); + + if (json.path(POLICY_ID).isMissingNode() || pid == null) { + // TODO: handle errors + return null; + } + + TunnelPolicy.Builder tpb = TunnelPolicy.builder().setPolicyId(pid); + if (!json.path(TYPE).isMissingNode() && type != null && + Policy.Type.valueOf(type).equals(Policy.Type.TUNNEL_FLOW)) { + + if (json.path(TUNNEL_ID).isMissingNode() || tunnelId == null) { + return null; + } + + tpb.setTunnelId(tunnelId); + tpb.setType(Policy.Type.valueOf(type)); + + if (!json.path(PRIORITY).isMissingNode()) { + tpb.setPriority(priority); + } + if (dstIp != null) { + tpb.setDstIp(dstIp); + } + if (srcIp != null) { + tpb.setSrcIp(srcIp); + } + if (protoType != null) { + tpb.setIpProto(protoType); + } + if (dstPort != 0) { + tpb.setDstPort(dstPort); + } + if (srcPort != 0) { + tpb.setSrcPort(srcPort); + } + } + + return tpb.build(); + } + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/PolicyWebResource.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/PolicyWebResource.java new file mode 100644 index 00000000..cdc53a02 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/PolicyWebResource.java @@ -0,0 +1,108 @@ +/* + * Copyright 2015 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.segmentrouting.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import org.onosproject.rest.AbstractWebResource; +import org.onosproject.segmentrouting.Policy; +import org.onosproject.segmentrouting.SegmentRoutingService; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +/** + * Query, create and remove segment routing plicies. + */ +@Path("policy") +public class PolicyWebResource extends AbstractWebResource { + + private static final PolicyCodec POLICY_CODEC = new PolicyCodec(); + + /** + * Get all segment routing policies. + * Returns an array of segment routing policies. + * + * @return status of OK + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response getPolicy() { + SegmentRoutingService srService = get(SegmentRoutingService.class); + List<Policy> policies = srService.getPolicies(); + ObjectNode result = new ObjectMapper().createObjectNode(); + result.set("policy", new PolicyCodec().encode(policies, this)); + + return ok(result.toString()).build(); + } + + /** + * Create a new segment routing policy. + * + * @param input JSON stream for policy to create + * @return status of the request - OK if the policy is created, + * INTERNAL_SERVER_ERROR if the JSON is invalid or the policy cannot be created + * @throws IOException if JSON processing fails + */ + @POST + @Consumes(MediaType.APPLICATION_JSON) + public Response createPolicy(InputStream input) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode policyJson = (ObjectNode) mapper.readTree(input); + SegmentRoutingService srService = get(SegmentRoutingService.class); + Policy policyInfo = POLICY_CODEC.decode(policyJson, this); + + if (policyInfo.type() == Policy.Type.TUNNEL_FLOW) { + srService.createPolicy(policyInfo); + return Response.ok().build(); + } else { + return Response.serverError().build(); + } + } + + /** + * Delete a segment routing policy. + * + * @param input JSON stream for policy to delete + * @return status of the request - OK if the policy is removed, + * INTERNAL_SERVER_ERROR otherwise + * @throws IOException if JSON is invalid + */ + @DELETE + @Consumes(MediaType.APPLICATION_JSON) + public Response removePolicy(InputStream input) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode policyJson = (ObjectNode) mapper.readTree(input); + SegmentRoutingService srService = get(SegmentRoutingService.class); + Policy policyInfo = POLICY_CODEC.decode(policyJson, this); + // TODO: Check the result + srService.removePolicy(policyInfo); + + return Response.ok().build(); + + } + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/TunnelCodec.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/TunnelCodec.java new file mode 100644 index 00000000..2f85afd5 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/TunnelCodec.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015 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.segmentrouting.web; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.onosproject.segmentrouting.DefaultTunnel; +import org.onosproject.segmentrouting.Tunnel; + +import java.util.ArrayList; +import java.util.List; + +public final class TunnelCodec extends JsonCodec<Tunnel> { + + // JSON field names + private static final String TUNNEL_ID = "tunnel_id"; + private static final String GROUP_ID = "group_id"; + private static final String LABEL_PATH = "label_path"; + + @Override + public ObjectNode encode(Tunnel tunnel, CodecContext context) { + final ObjectNode result = context.mapper().createObjectNode() + .put(TUNNEL_ID, tunnel.id()); + + result.put(GROUP_ID, tunnel.groupId()); + + final ArrayNode jsonLabelIds = result.putArray(LABEL_PATH); + + tunnel.labelIds().forEach(label -> jsonLabelIds.add(label.intValue())); + + return result; + } + + @Override + public DefaultTunnel decode(ObjectNode json, CodecContext context) { + + String tid = json.path(TUNNEL_ID).asText(); + List<Integer> labels = new ArrayList<>(); + + if (!json.path(LABEL_PATH).isMissingNode()) { + ArrayNode labelArray = (ArrayNode) json.path(LABEL_PATH); + for (JsonNode o : labelArray) { + labels.add(o.asInt()); + } + } + + return new DefaultTunnel(tid, labels); + } + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/TunnelWebResource.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/TunnelWebResource.java new file mode 100644 index 00000000..fb30308a --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/TunnelWebResource.java @@ -0,0 +1,102 @@ +/* + * Copyright 2015 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.segmentrouting.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import org.onosproject.rest.AbstractWebResource; +import org.onosproject.segmentrouting.SegmentRoutingService; +import org.onosproject.segmentrouting.Tunnel; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +/** + * Query, create and remove segment routing tunnels. + */ +@Path("tunnel") +public class TunnelWebResource extends AbstractWebResource { + + private static final TunnelCodec TUNNEL_CODEC = new TunnelCodec(); + + /** + * Get all segment routing tunnels. + * Returns an array of segment routing tunnels. + * + * @return status of OK + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response getTunnel() { + SegmentRoutingService srService = get(SegmentRoutingService.class); + List<Tunnel> tunnels = srService.getTunnels(); + ObjectNode result = new ObjectMapper().createObjectNode(); + result.set("tunnel", new TunnelCodec().encode(tunnels, this)); + + return ok(result.toString()).build(); + } + + /** + * Create a new segment routing tunnel. + * + * @param input JSON stream for tunnel to create + * @return status of the request - OK if the tunnel is created, + * INTERNAL_SERVER_ERROR if the JSON is invalid or the tunnel cannot be created + * @throws IOException if the JSON is invalid + */ + @POST + @Consumes(MediaType.APPLICATION_JSON) + public Response createTunnel(InputStream input) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode tunnelJson = (ObjectNode) mapper.readTree(input); + SegmentRoutingService srService = get(SegmentRoutingService.class); + Tunnel tunnelInfo = TUNNEL_CODEC.decode(tunnelJson, this); + srService.createTunnel(tunnelInfo); + + return Response.ok().build(); + } + + /** + * Delete a segment routing tunnel. + * + * @param input JSON stream for tunnel to delete + * @return status of the request - OK if the tunnel is removed, + * INTERNAL_SERVER_ERROR otherwise + * @throws IOException if JSON is invalid + */ + @DELETE + @Consumes(MediaType.APPLICATION_JSON) + public Response removeTunnel(InputStream input) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode tunnelJson = (ObjectNode) mapper.readTree(input); + SegmentRoutingService srService = get(SegmentRoutingService.class); + Tunnel tunnelInfo = TUNNEL_CODEC.decode(tunnelJson, this); + srService.removeTunnel(tunnelInfo); + + return Response.ok().build(); + } + +} diff --git a/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/package-info.java b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/package-info.java new file mode 100644 index 00000000..85c63de5 --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/java/org/onosproject/segmentrouting/web/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 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. + */ + +/** + * Set of resources implementing the segment routing application REST API. + */ +package org.onosproject.segmentrouting.web;
\ No newline at end of file diff --git a/framework/src/onos/apps/segmentrouting/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/framework/src/onos/apps/segmentrouting/src/main/resources/OSGI-INF/blueprint/shell-config.xml new file mode 100644 index 00000000..3e47bf8f --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/resources/OSGI-INF/blueprint/shell-config.xml @@ -0,0 +1,40 @@ +<!-- + ~ Copyright 2015 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. + --> +<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"> + + <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0"> + <command> + <action class="org.onosproject.segmentrouting.cli.TunnelListCommand"/> + </command> + <command> + <action class="org.onosproject.segmentrouting.cli.PolicyListCommand"/> + </command> + <command> + <action class="org.onosproject.segmentrouting.cli.PolicyAddCommand"/> + </command> + <command> + <action class="org.onosproject.segmentrouting.cli.PolicyRemoveCommand"/> + </command> + <command> + <action class="org.onosproject.segmentrouting.cli.TunnelAddCommand"/> + </command> + <command> + <action class="org.onosproject.segmentrouting.cli.TunnelRemoveCommand"/> + </command> + </command-bundle> +</blueprint> + + diff --git a/framework/src/onos/apps/segmentrouting/src/main/webapp/WEB-INF/web.xml b/framework/src/onos/apps/segmentrouting/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..94c0d7de --- /dev/null +++ b/framework/src/onos/apps/segmentrouting/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2015 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. + --> +<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" + xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" + id="ONOS" version="2.5"> + <display-name>Segment Routing REST API v1.0</display-name> + + <servlet> + <servlet-name>JAX-RS Service</servlet-name> + <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> + <init-param> + <param-name>com.sun.jersey.config.property.resourceConfigClass</param-name> + <param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value> + </init-param> + <init-param> + <param-name>com.sun.jersey.config.property.classnames</param-name> + <param-value> + org.onosproject.segmentrouting.web.TunnelWebResource, + org.onosproject.segmentrouting.web.PolicyWebResource + </param-value> + </init-param> + <load-on-startup>1</load-on-startup> + </servlet> + + <servlet-mapping> + <servlet-name>JAX-RS Service</servlet-name> + <url-pattern>/*</url-pattern> + </servlet-mapping> +</web-app> |