blob: f5d8ed1707dfaef23ff883accee60269b4485eee [file] [log] [blame]
/*
* Copyright 2015-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.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 Devices (target devices) 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 < (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<>();
distanceSwArray.add(reachedDevice);
distanceDeviceMap.put(currDistance + 1, distanceSwArray);
} else {
distanceSwArray.add(reachedDevice);
}
}
ArrayList<Link> upstreamLinkArray =
upstreamLinks.get(reachedDevice);
if (upstreamLinkArray == null) {
upstreamLinkArray = new ArrayList<>();
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 < (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<>();
distanceSwArray.add(reachedDevice);
distanceDeviceMap.put(currDistance + 1, distanceSwArray);
} else {
distanceSwArray.add(reachedDevice);
}
}
ArrayList<Link> upstreamLinkArray =
upstreamLinks.get(reachedDevice);
if (upstreamLinkArray == null) {
upstreamLinkArray = new ArrayList<>();
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<>();
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 Devices 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<>();
for (Integer itrIndx : distanceDeviceMap.keySet()) {
HashMap<DeviceId, ArrayList<Path>> swMap = new HashMap<>();
for (DeviceId sw : distanceDeviceMap.get(itrIndx)) {
swMap.put(sw, getECMPPaths(sw));
}
pathGraph.put(itrIndx, swMap);
}
return pathGraph;
}
/**
* Returns the complete info of the computed ECMP paths for each target device
* learned in multiple iterations from the root Device. The computed info
* returned is per iteration (Integer key of outer HashMap). In each
* iteration, for the target devices reached (DeviceId key of inner HashMap),
* the ECMP paths are detailed (2D array).
*
* @return the hash table of target Devices learned in multiple Dijkstra
* iterations and corresponding ECMP paths in terms of Devices to
* be traversed (via) from the root Device to the target Device
*/
public HashMap<Integer, HashMap<DeviceId,
ArrayList<ArrayList<DeviceId>>>> getAllLearnedSwitchesAndVia() {
HashMap<Integer, HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>>> deviceViaMap = new HashMap<>();
for (Integer itrIndx : distanceDeviceMap.keySet()) {
HashMap<DeviceId, ArrayList<ArrayList<DeviceId>>> swMap = new HashMap<>();
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 = DefaultLink.builder()
.providerId(src.providerId())
.src(src.src())
.dst(src.dst())
.type(src.type())
.annotations(src.annotations())
.build();
return defaultLink;
}
@Override
public String toString() {
StringBuilder sBuilder = new StringBuilder();
for (Device device: srManager.deviceService.getDevices()) {
if (device.id() != rootDevice) {
sBuilder.append("\r\n Paths from " + rootDevice + " to "
+ device.id() + "\r\n");
ArrayList<Path> paths = getECMPPaths(device.id());
if (paths != null) {
for (Path path : paths) {
sBuilder.append("\r\n == "); // equal cost paths delimiter
for (Link link : path.links()) {
sBuilder.append(" : " + link.src() + " -> " + link.dst());
}
}
}
}
}
return sBuilder.toString();
}
}