blob: e48ab0ca949c7ffd6d9e2ad232f6a9d5bf30c4e2 [file] [log] [blame]
package net.onrc.onos.apps.segmentrouting;
import java.util.ArrayList;
import java.util.List;
import net.floodlightcontroller.core.IOF13Switch;
import net.floodlightcontroller.core.IOF13Switch.NeighborSet;
import net.onrc.onos.core.drivermanager.OFSwitchImplDellOSR;
import net.onrc.onos.core.topology.Link;
import net.onrc.onos.core.topology.Switch;
import net.onrc.onos.core.util.Dpid;
import net.onrc.onos.core.util.PortNumber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SegmentRoutingTunnel {
private static final Logger log = LoggerFactory
.getLogger(SegmentRoutingTunnel.class);
private String tunnelId;
private List<Integer> labelIds;
private List<TunnelRouteInfo> routes;
private SegmentRoutingManager srManager;
private final int MAX_NUM_LABELS = 3;
/**
* Constructor
*
* @param srm SegmentRoutingManager object
* @param tid Tunnel ID
* @param labelIds Label stack of the tunnel
*/
public SegmentRoutingTunnel(SegmentRoutingManager srm, String tid,
List<Integer> labelIds) {
this.srManager = srm;
this.tunnelId = tid;
this.labelIds = labelIds;
this.routes = new ArrayList<TunnelRouteInfo>();
}
/**
* Get tunnel ID
*
* @return tunnel ID
*/
public String getTunnelId(){
return this.tunnelId;
}
/**
* Get Nodes IDs for the tunnel including source and destination router
*
* @return List of Node ID
*/
public List<Integer> getLabelids() {
return this.labelIds;
}
/**
* Get tunnel information after stitching if necessary
*
* @return List of TunnelRouteInfo object
*/
public List<TunnelRouteInfo> getRoutes(){
return this.routes;
}
/**
* Create a tunnel
* It requests the driver to create a group chaining for the tunnel.
*
* @return true if succeeds, false otherwise
*/
public boolean createTunnel() {
if (labelIds.isEmpty() || labelIds.size() < 2) {
log.debug("Wrong tunnel information");
return false;
}
List<String> Ids = new ArrayList<String>();
for (Integer label : labelIds) {
Ids.add(label.toString());
}
List<TunnelRouteInfo> stitchingRule = getStitchingRule(Ids);
if (stitchingRule == null) {
log.debug("Failed to get a tunnel rule.");
return false;
}
for (TunnelRouteInfo route: stitchingRule) {
NeighborSet ns = new NeighborSet();
for (Dpid dpid: route.getFwdSwDpid())
ns.addDpid(dpid);
printTunnelInfo(route.srcSwDpid, tunnelId, route.getRoute(), ns);
int groupId = -1;
if ((groupId =createGroupsForTunnel(tunnelId, route, ns)) < 0) {
log.debug("Failed to create a tunnel at driver.");
return false;
}
route.setGroupId(groupId);
}
this.routes = stitchingRule;
return true;
}
/**
* Remove the tunnel.
* It requests driver to remove all groups for the tunnel
*
* @return true if succeeds, false otherwise.
*/
public boolean removeTunnel() {
for (TunnelRouteInfo route: routes) {
IOF13Switch sw13 = srManager.getIOF13Switch(route.srcSwDpid);
if (sw13 == null) {
log.warn("Cannot find the switch", route.srcSwDpid);
return false;
}
else {
if (!sw13.removeGroup(route.getGroupId())) {
log.warn("Faied to remove the tunnel {} at driver",
tunnelId);
return false;
}
}
}
return true;
}
/**
* Create groups for the tunnel
*
* @param tunnelId tunnel ID
* @param routeInfo label stacks for the tunnel
* @param ns NeighborSet to forward packets
* @return group ID, return -1 if it fails
*/
private int createGroupsForTunnel(String tunnelId, TunnelRouteInfo routeInfo,
NeighborSet ns) {
IOF13Switch targetSw = srManager.getIOF13Switch(routeInfo.srcSwDpid);
if (targetSw == null) {
log.debug("Switch {} is gone.", routeInfo.srcSwDpid);
return -1;
}
List<Integer> Ids = new ArrayList<Integer>();
for (String IdStr: routeInfo.route)
Ids.add(Integer.parseInt(IdStr));
List<PortNumber> ports = getPortsFromNeighborSet(routeInfo.srcSwDpid, ns);
int groupId = targetSw.createGroup(Ids, ports);
return groupId;
}
/**
* Split the nodes IDs into multiple tunnel if Segment Stitching is required.
* We assume that the first node ID is the one of source router, and the last
* node ID is that of the destination router.
*
* @param route list of node IDs
* @return List of the TunnelRoutInfo
*/
private List<TunnelRouteInfo> getStitchingRule(List<String> route) {
if (route.isEmpty() || route.size() < 3)
return null;
List<TunnelRouteInfo> rules = new ArrayList<TunnelRouteInfo>();
Switch srcSw = srManager.getSwitchFromNodeId(route.get(0));
if (srcSw == null) {
log.warn("Switch is not found for Node SID {}", route.get(0));
return null;
}
String srcDpid = srcSw.getDpid().toString();
int i = 0;
TunnelRouteInfo routeInfo = new TunnelRouteInfo();
boolean checkNeighbor = false;
String prevAdjacencySid = null;
String prevNodeId = null;
for (String nodeId: route) {
// The first node ID is always the source router.
// We assume that the first ID cannot be an Adjacency SID.
if (i == 0) {
srcSw = srManager.getSwitchFromNodeId(nodeId);
if (srcDpid == null)
srcDpid = srcSw.getDpid().toString();
routeInfo.setSrcDpid(srcDpid);
checkNeighbor = true;
i++;
}
// if this is the first node ID to put the label stack..
else if (i == 1) {
if (checkNeighbor) {
List<Dpid> fwdSws = getDpidIfNeighborOf(nodeId, srcSw);
// if nodeId is NOT the neighbor of srcSw..
if (fwdSws.isEmpty()) {
fwdSws = srManager.getForwardingSwitchForNodeId(srcSw,nodeId);
if (fwdSws == null || fwdSws.isEmpty()) {
log.warn("There is no route from node {} to node {}",
srcSw.getDpid(), nodeId);
return null;
}
routeInfo.addRoute(nodeId);
i++;
}
routeInfo.setFwdSwDpid(fwdSws);
// we check only the next node ID of the source router
checkNeighbor = false;
}
// if neighbor check is already done, then just add it
else {
routeInfo.addRoute(nodeId);
i++;
}
}
// if i > 1
else {
// If the adjacency SID is pushed and the next SID is the destination
// of the adjacency SID, then do not add the SID.
if (prevAdjacencySid != null) {
if (isAdjacencySidNeighborOf(prevNodeId, prevAdjacencySid, nodeId)) {
prevAdjacencySid = null;
prevNodeId = nodeId;
continue;
}
prevAdjacencySid = null;
}
routeInfo.addRoute(nodeId);
i++;
}
// If the adjacency ID is added the label stack,
// then we need to check if the next node is the destination of the adjacency SID
if (srManager.isAdjacencySid(nodeId))
prevAdjacencySid = nodeId;
// If the number of labels reaches the limit, start over the procedure
if (i == MAX_NUM_LABELS +1) {
rules.add(routeInfo);
routeInfo = new TunnelRouteInfo();
if (srManager.isAdjacencySid(nodeId)) {
// If the previous sub tunnel finishes with adjacency SID,
// then we need to start the procedure from the adjacency
// destination ID.
List<Switch> destNodeList =
getAdjacencyDestinationNode(prevNodeId, nodeId);
if (destNodeList == null || destNodeList.isEmpty()) {
log.warn("Cannot find destination node for adjacencySID {}",
nodeId);
return null;
}
// If the previous sub tunnel finishes with adjacency SID with
// multiple ports, then we need to remove the adjacency Sid
// from the previous sub tunnel and start the new sub tunnel
// with the adjacency Sid. Technically, the new subtunnel
// forward packets to the port assigned to the adjacency Sid
// and the label stack starts with the next ID.
// This is to avoid to install new policy rule to multiple nodes for stitching when the
// adjacency Sid that has more than one port.
if (destNodeList.size() > 1) {
rules.get(rules.size()-1).route.remove(nodeId);
srcSw = srManager.getSwitchFromNodeId(prevNodeId);
List<Dpid> fwdSws = getDpidIfNeighborOf(nodeId, srcSw);
routeInfo.setFwdSwDpid(fwdSws);
routeInfo.setSrcDpid(srcSw.getDpid().toString());
i = 1;
checkNeighbor = false;
continue;
}
else {
srcSw = destNodeList.get(0);
}
}
else {
srcSw = srManager.getSwitchFromNodeId(nodeId);
}
srcDpid = srcSw.getDpid().toString();
routeInfo.setSrcDpid(srcDpid);
i = 1;
checkNeighbor = true;
}
if (prevAdjacencySid == null)
prevNodeId = nodeId;
}
if (i < MAX_NUM_LABELS+1 && (routeInfo.getFwdSwDpid() != null &&
!routeInfo.getFwdSwDpid().isEmpty())) {
rules.add(routeInfo);
// NOTE: empty label stack can happen, but forwarding destination should be set
}
return rules;
}
/**
* Get port numbers of the neighbor set.
* If ECMP in transit router is not supported, then only one port should be returned
* regardless of number of nodes in neighbor set.
*
* @param srcSwDpid source switch
* @param ns Neighbor set of the switch
* @return List of PortNumber, null if not found
*/
private List<PortNumber> getPortsFromNeighborSet(String srcSwDpid, NeighborSet ns) {
List<PortNumber> portList = new ArrayList<PortNumber>();
Switch srcSwitch = srManager.getSwitch(srcSwDpid);
if (srcSwitch == null)
return null;
IOF13Switch srcSwitch13 =
srManager.getIOF13Switch(srcSwitch.getDpid().toString());
for (Dpid neighborDpid: ns.getDpids()) {
if (srcSwitch13 instanceof OFSwitchImplDellOSR &&
ns.getDpids().size() == 1) {
Switch dstSwitch = srManager.getSwitch(neighborDpid.toString());
if (srManager.isTransitRouter(srcSwitch) &&
srManager.isTransitRouter(dstSwitch)) {
Link link = srcSwitch.getLinkToNeighbor(neighborDpid);
portList.add(link.getSrcPort().getNumber());
break;
}
}
else {
for (Link link: srcSwitch.getOutgoingLinks()) {
if (link.getDstSwitch().getDpid().equals(neighborDpid)) {
portList.add(link.getSrcPort().getNumber());
}
}
}
}
return portList;
}
/**
* Get the DPID of the router with node ID IF the node ID is the neighbor of the
* Switch srcSW.
* If the nodeId is the adjacency Sid, then it returns the destination router DPIDs.
*
* @param nodeId Node ID to check
* @param srcSw target Switch
* @return List of DPID of nodeId, empty list if the nodeId is not the neighbor of srcSW
*/
private List<Dpid> getDpidIfNeighborOf(String nodeId, Switch srcSw) {
List<Dpid> fwdSws = new ArrayList<Dpid>();
// if the nodeID is the adjacency ID, then we need to regard it as the
// neighbor node ID and need to return the destination router DPID(s)
if (srManager.isAdjacencySid(nodeId)) {
String srcNodeId = srManager.getMplsLabel(srcSw.getDpid().toString());
List<Integer> ports =
srManager.getAdacencyPorts(Integer.parseInt(srcNodeId),
Integer.parseInt(nodeId));
for (Integer port: ports) {
for (Link link: srcSw.getOutgoingLinks()) {
if (link.getSrcPort().getPortNumber().value() == port) {
fwdSws.add(link.getDstSwitch().getDpid());
}
}
}
}
else {
List<Dpid> fwdSwDpids =
srManager.getForwardingSwitchForNodeId(srcSw,nodeId);
if (fwdSwDpids == null || fwdSwDpids.isEmpty()) {
log.warn("There is no route from node {} to node {}",
srcSw.getDpid(), nodeId);
return fwdSws;
}
for (Dpid dpid: fwdSwDpids) {
String id = srManager.getMplsLabel(dpid.toString()).toString();
if (id.equals(nodeId)) {
fwdSws.add(dpid);
break;
}
}
}
return fwdSws;
}
/**
* Check whether the router with preNodeid is connected to the router
* with nodeId via adjacencySid or not
*
* @param prevNodeId the router node ID of the adjacencySid
* @param adjacencySid adjacency SID
* @param nodeId the router node ID to check
* @return
*/
private boolean isAdjacencySidNeighborOf(String prevNodeId, String adjacencySid, String nodeId) {
List<Integer> ports =
srManager.getAdacencyPorts(Integer.valueOf(prevNodeId),
Integer.valueOf(adjacencySid));
if (ports == null) {
log.warn("Cannot find ports for node ID {} and adjacencySID ",
prevNodeId, adjacencySid);
return false;
}
for (Integer port: ports) {
Switch sw = srManager.getSwitchFromNodeId(prevNodeId);
for (Link link: sw.getOutgoingLinks()) {
if (link.getSrcPort().getPortNumber().value() == port) {
String linkDstDpid = link.getDstPort().getDpid().toString();
String linkDstId = srManager.getMplsLabel(linkDstDpid);
if (linkDstId.equals(nodeId)) {
return true;
}
}
}
}
return false;
}
/**
* Get the destination Nodes of the adjacency Sid
*
* @param nodeId node ID of the adjacency Sid
* @param adjacencySid adjacency Sid
* @return List of Switch, empty list if not found
*/
private List<Switch> getAdjacencyDestinationNode(String nodeId, String adjacencySid) {
List<Switch> dstSwList = new ArrayList<Switch>();
List<Integer> ports = srManager.getAdacencyPorts(Integer.valueOf(nodeId),
Integer.valueOf(adjacencySid));
Switch srcSw = srManager.getSwitchFromNodeId(nodeId);
for (Integer port: ports) {
for (Link link: srcSw.getOutgoingLinks()) {
if (link.getSrcPort().getPortNumber().value() == port) {
dstSwList.add(link.getDstSwitch());
}
}
}
return dstSwList;
}
/**
* print tunnel info - used only for debugging.
* @param targetSw
*
* @param fwdSwDpids
* @param ids
* @param tunnelId
*/
private void printTunnelInfo(String targetSw, String tunnelId,
List<String> ids, NeighborSet ns) {
StringBuilder logStr = new StringBuilder("In switch " +
targetSw + ", create a tunnel " + tunnelId + " " + " of push ");
for (String id: ids)
logStr.append(id + "-");
logStr.append(" output to ");
for (Dpid dpid: ns.getDpids())
logStr.append(dpid + " - ");
log.debug(logStr.toString());
}
public class TunnelRouteInfo {
private String srcSwDpid;
private List<Dpid> fwdSwDpids;
private List<String> route;
private int gropuId;
public TunnelRouteInfo() {
fwdSwDpids = new ArrayList<Dpid>();
route = new ArrayList<String>();
}
private void setSrcDpid(String dpid) {
this.srcSwDpid = dpid;
}
private void setFwdSwDpid(List<Dpid> dpid) {
this.fwdSwDpids = dpid;
}
private void addRoute(String id) {
route.add(id);
}
private void setGroupId(int groupId) {
this.gropuId = groupId;
}
public String getSrcSwDpid() {
return this.srcSwDpid;
}
public List<Dpid> getFwdSwDpid() {
return this.fwdSwDpids;
}
public List<String> getRoute() {
return this.route;
}
public int getGroupId() {
return this.gropuId;
}
}
}