use of org.onosproject.net.DeviceId in project trellis-control by opennetworkinglab.
the class McastHandler method recoverFailure.
/**
* General failure recovery procedure.
*
* @param mcastIp the group to recover
* @param failedElement the failed element
*/
private void recoverFailure(IpAddress mcastIp, Object failedElement) {
// Do not proceed if we are not the leaders
if (!mcastUtils.isLeader(mcastIp)) {
log.debug("Skip {} due to lack of leadership", mcastIp);
return;
}
// Skip if it is not an infra failure
Set<DeviceId> transitDevices = getDevice(mcastIp, TRANSIT);
if (!mcastUtils.isInfraFailure(transitDevices, failedElement)) {
log.debug("Skip {} not an infrastructure failure", mcastIp);
return;
}
// Do not proceed if the sources of this group are missing
Set<ConnectPoint> sources = getSources(mcastIp);
if (sources.isEmpty()) {
log.warn("Missing sources for group {}", mcastIp);
return;
}
// Get all the paths, affected paths, good links and good devices
Set<List<Link>> storedPaths = getStoredPaths(mcastIp);
Set<List<Link>> affectedPaths = mcastUtils.getAffectedPaths(storedPaths, failedElement);
Set<Link> goodLinks = Sets.newHashSet();
Map<DeviceId, Set<DeviceId>> goodDevicesBySource = Maps.newHashMap();
Map<DeviceId, Set<ConnectPoint>> processedSourcesByEgress = Maps.newHashMap();
Sets.difference(storedPaths, affectedPaths).forEach(goodPath -> {
goodLinks.addAll(goodPath);
DeviceId srcDevice = goodPath.get(0).src().deviceId();
Set<DeviceId> goodDevices = Sets.newHashSet();
goodPath.forEach(link -> goodDevices.add(link.src().deviceId()));
goodDevicesBySource.compute(srcDevice, (k, v) -> {
v = v == null ? Sets.newHashSet() : v;
v.addAll(goodDevices);
return v;
});
});
affectedPaths.forEach(affectedPath -> {
// TODO remove
log.info("Good links {}", goodLinks);
// TODO remove
log.info("Good devices {}", goodDevicesBySource);
// TODO trace
log.info("Healing the path {}", affectedPath);
DeviceId srcDevice = affectedPath.get(0).src().deviceId();
DeviceId dstDevice = affectedPath.get(affectedPath.size() - 1).dst().deviceId();
// Fix in one shot multiple sources
Set<ConnectPoint> affectedSources = sources.stream().filter(device -> device.deviceId().equals(srcDevice)).collect(Collectors.toSet());
Set<ConnectPoint> processedSources = processedSourcesByEgress.getOrDefault(dstDevice, Collections.emptySet());
Optional<Path> alternativePath = getPath(srcDevice, dstDevice, mcastIp, null);
// If an alternative is possible go ahead
if (alternativePath.isPresent()) {
// TODO trace
log.info("Alternative path {}", alternativePath.get().links());
} else {
// Otherwise try to come up with an alternative
// TODO trace
log.info("No alternative path");
Set<ConnectPoint> notAffectedSources = Sets.difference(sources, affectedSources);
Set<ConnectPoint> remainingSources = Sets.difference(notAffectedSources, processedSources);
alternativePath = recoverSinks(dstDevice, mcastIp, affectedSources, remainingSources);
processedSourcesByEgress.compute(dstDevice, (k, v) -> {
v = v == null ? Sets.newHashSet() : v;
v.addAll(affectedSources);
return v;
});
}
// Recover from the failure if possible
Optional<Path> finalPath = alternativePath;
affectedSources.forEach(affectedSource -> {
// Update the mcastPath store
McastPathStoreKey mcastPathStoreKey = new McastPathStoreKey(mcastIp, affectedSource);
// Verify if there are local sinks
Set<DeviceId> localSinks = getSinks(mcastIp, srcDevice, affectedSource).stream().map(ConnectPoint::deviceId).collect(Collectors.toSet());
Set<DeviceId> goodDevices = goodDevicesBySource.compute(affectedSource.deviceId(), (k, v) -> {
v = v == null ? Sets.newHashSet() : v;
v.addAll(localSinks);
return v;
});
// TODO remove
log.info("Good devices {}", goodDevicesBySource);
Collection<? extends List<Link>> storedPathsBySource = Versioned.valueOrElse(mcastPathStore.get(mcastPathStoreKey), Lists.newArrayList());
Optional<? extends List<Link>> storedPath = storedPathsBySource.stream().filter(path -> path.equals(affectedPath)).findFirst();
// Remove bad links
affectedPath.forEach(affectedLink -> {
DeviceId affectedDevice = affectedLink.src().deviceId();
// If there is overlap with good paths - skip it
if (!goodLinks.contains(affectedLink)) {
removePortFromDevice(affectedDevice, affectedLink.src().port(), mcastIp, mcastUtils.assignedVlan(affectedDevice.equals(affectedSource.deviceId()) ? affectedSource : null));
}
// Remove role on the affected links if last
if (!goodDevices.contains(affectedDevice)) {
mcastRoleStore.remove(new McastRoleStoreKey(mcastIp, affectedDevice, affectedSource));
}
});
// trying with the original object as workaround
if (storedPath.isPresent()) {
mcastPathStore.remove(mcastPathStoreKey, storedPath.get());
} else {
log.warn("Unable to find the corresponding path - trying removeal");
mcastPathStore.remove(mcastPathStoreKey, affectedPath);
}
// Program new links
if (finalPath.isPresent()) {
List<Link> links = finalPath.get().links();
installPath(mcastIp, affectedSource, links);
mcastPathStore.put(mcastPathStoreKey, links);
links.forEach(link -> goodDevices.add(link.src().deviceId()));
goodDevicesBySource.compute(srcDevice, (k, v) -> {
v = v == null ? Sets.newHashSet() : v;
v.addAll(goodDevices);
return v;
});
goodLinks.addAll(finalPath.get().links());
}
});
});
}
use of org.onosproject.net.DeviceId in project trellis-control by opennetworkinglab.
the class McastHandler method getSinks.
/**
* Gets sink(s) of given multicast group.
*
* @param mcastIp multicast IP
* @return set of connect point or empty set if not found
*/
private Set<ConnectPoint> getSinks(IpAddress mcastIp, DeviceId device, ConnectPoint source) {
McastPathStoreKey pathStoreKey = new McastPathStoreKey(mcastIp, source);
Collection<? extends List<Link>> storedPaths = Versioned.valueOrElse(mcastPathStore.get(pathStoreKey), Lists.newArrayList());
VlanId assignedVlan = mcastUtils.assignedVlan(device.equals(source.deviceId()) ? source : null);
McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, device, assignedVlan);
NextObjective nextObjective = Versioned.valueOrNull(mcastNextObjStore.get(mcastStoreKey));
ImmutableSet.Builder<ConnectPoint> cpBuilder = ImmutableSet.builder();
if (nextObjective != null) {
Set<PortNumber> outputPorts = mcastUtils.getPorts(nextObjective.next());
outputPorts.forEach(portNumber -> cpBuilder.add(new ConnectPoint(device, portNumber)));
}
Set<ConnectPoint> egressCp = cpBuilder.build();
return egressCp.stream().filter(connectPoint -> !mcastUtils.isInfraPort(connectPoint, storedPaths)).collect(Collectors.toSet());
}
use of org.onosproject.net.DeviceId in project trellis-control by opennetworkinglab.
the class McastHandler method buildMcastPaths.
/**
* Build the mcast paths.
*
* @param storedPaths mcast tree
* @param mcastIp the group ip
* @param source the source
*/
private Map<ConnectPoint, List<ConnectPoint>> buildMcastPaths(Collection<? extends List<Link>> storedPaths, IpAddress mcastIp, ConnectPoint source) {
Map<ConnectPoint, List<ConnectPoint>> mcastTree = Maps.newHashMap();
// Local sinks
Set<ConnectPoint> localSinks = getSinks(mcastIp, source.deviceId(), source);
localSinks.forEach(localSink -> mcastTree.put(localSink, Lists.newArrayList(localSink, source)));
// Remote sinks
storedPaths.forEach(path -> {
List<Link> links = path;
DeviceId egressDevice = links.get(links.size() - 1).dst().deviceId();
Set<ConnectPoint> remoteSinks = getSinks(mcastIp, egressDevice, source);
List<ConnectPoint> connectPoints = Lists.newArrayList(source);
links.forEach(link -> {
connectPoints.add(link.src());
connectPoints.add(link.dst());
});
Collections.reverse(connectPoints);
remoteSinks.forEach(remoteSink -> {
List<ConnectPoint> finalPath = Lists.newArrayList(connectPoints);
finalPath.add(0, remoteSink);
mcastTree.put(remoteSink, finalPath);
});
});
return mcastTree;
}
use of org.onosproject.net.DeviceId in project trellis-control by opennetworkinglab.
the class McastHandler method removePortFromDevice.
/**
* Removes a port from given multicast group on given device.
* This involves the update of L3 multicast group and multicast routing
* table entry.
*
* @param deviceId device ID
* @param port port to be added
* @param mcastIp multicast group
* @param assignedVlan assigned VLAN ID
* @return true if this is the last sink on this device
*/
private boolean removePortFromDevice(DeviceId deviceId, PortNumber port, IpAddress mcastIp, VlanId assignedVlan) {
// TODO trace
log.info("Removing {} on {}/{} and vlan {}", mcastIp, deviceId, port, assignedVlan);
McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId, assignedVlan);
// This device is not serving this multicast group
if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
return true;
}
NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
Set<PortNumber> existingPorts = mcastUtils.getPorts(nextObj.next());
// This port does not serve this multicast group
if (!existingPorts.contains(port)) {
if (!existingPorts.isEmpty()) {
log.debug("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
return false;
}
return true;
}
// Copy and modify the ImmutableSet
existingPorts = Sets.newHashSet(existingPorts);
existingPorts.remove(port);
NextObjective newNextObj;
ObjectiveContext context;
ForwardingObjective fwdObj;
if (existingPorts.isEmpty()) {
context = new DefaultObjectiveContext((objective) -> log.debug("Successfully remove {} on {}/{}, vlan {}", mcastIp, deviceId, port.toLong(), assignedVlan), (objective, error) -> log.warn("Failed to remove {} on {}/{}, vlan {}: {}", mcastIp, deviceId, port.toLong(), assignedVlan, error));
fwdObj = mcastUtils.fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
if (!srManager.deviceConfiguration().isConfigured(deviceId)) {
log.debug("skip forward flowobjective removal for device: {}", deviceId);
} else {
srManager.flowObjectiveService.forward(deviceId, fwdObj);
}
mcastNextObjStore.remove(mcastStoreKey);
} else {
// Here we store the next objective with the remaining port
newNextObj = mcastUtils.nextObjBuilder(mcastIp, assignedVlan, existingPorts, nextObj.id()).removeFromExisting();
mcastNextObjStore.put(mcastStoreKey, newNextObj);
// Let's modify the next objective removing the bucket
newNextObj = mcastUtils.nextObjBuilder(mcastIp, assignedVlan, ImmutableSet.of(port), nextObj.id()).removeFromExisting();
if (!srManager.deviceConfiguration().isConfigured(deviceId)) {
log.debug("skip next flowobjective update for device: {}", deviceId);
} else {
// no need to update the flow here since we have updated the next objective + group
// the existing flow will keep pointing to the updated nextobj
srManager.flowObjectiveService.next(deviceId, newNextObj);
}
}
return existingPorts.isEmpty();
}
use of org.onosproject.net.DeviceId in project trellis-control by opennetworkinglab.
the class McastHandler method processSinksToBeAdded.
/**
* Process all the sinks related to a mcast group and return
* the ones to be processed.
*
* @param source the source connect point
* @param mcastIp the group address
* @param sinks the sinks to be evaluated
* @return the set of the sinks to be processed
*/
private Set<ConnectPoint> processSinksToBeAdded(ConnectPoint source, IpAddress mcastIp, Map<HostId, Set<ConnectPoint>> sinks) {
final Set<ConnectPoint> sinksToBeProcessed = Sets.newHashSet();
log.debug("Processing sinks to be added for Multicast group {}, source {}", mcastIp, source);
sinks.forEach(((hostId, connectPoints) -> {
// add all connect points that are not tied with any host
if (Objects.equal(HostId.NONE, hostId)) {
sinksToBeProcessed.addAll(connectPoints);
return;
}
// If it has more than 2 locations
if (connectPoints.size() > 2 || connectPoints.size() == 0) {
log.debug("Skip {} since sink {} has {} locations", mcastIp, hostId, connectPoints.size());
return;
}
// If it has one location, just use it
if (connectPoints.size() == 1) {
sinksToBeProcessed.add(connectPoints.stream().findFirst().orElse(null));
return;
}
// We prefer to reuse existing flows
ConnectPoint sinkToBeProcessed = connectPoints.stream().filter(connectPoint -> {
if (!isSinkForGroup(mcastIp, connectPoint, source)) {
return false;
}
if (!isSinkReachable(mcastIp, connectPoint, source)) {
return false;
}
ConnectPoint other = connectPoints.stream().filter(remaining -> !remaining.equals(connectPoint)).findFirst().orElse(null);
// We are already serving the sink
return !isSinkForSource(mcastIp, other, source);
}).findFirst().orElse(null);
if (sinkToBeProcessed != null) {
sinksToBeProcessed.add(sinkToBeProcessed);
return;
}
// Otherwise we prefer to reuse existing egresses
Set<DeviceId> egresses = getDevice(mcastIp, EGRESS, source);
sinkToBeProcessed = connectPoints.stream().filter(connectPoint -> {
if (!egresses.contains(connectPoint.deviceId())) {
return false;
}
if (!isSinkReachable(mcastIp, connectPoint, source)) {
return false;
}
ConnectPoint other = connectPoints.stream().filter(remaining -> !remaining.equals(connectPoint)).findFirst().orElse(null);
return !isSinkForSource(mcastIp, other, source);
}).findFirst().orElse(null);
if (sinkToBeProcessed != null) {
sinksToBeProcessed.add(sinkToBeProcessed);
return;
}
// Otherwise we prefer a location co-located with the source (if it exists)
sinkToBeProcessed = connectPoints.stream().filter(connectPoint -> connectPoint.deviceId().equals(source.deviceId())).findFirst().orElse(null);
if (sinkToBeProcessed != null) {
sinksToBeProcessed.add(sinkToBeProcessed);
return;
}
// Finally, we randomly pick a new location if it is reachable
sinkToBeProcessed = connectPoints.stream().filter(connectPoint -> {
if (!isSinkReachable(mcastIp, connectPoint, source)) {
return false;
}
ConnectPoint other = connectPoints.stream().filter(remaining -> !remaining.equals(connectPoint)).findFirst().orElse(null);
return !isSinkForSource(mcastIp, other, source);
}).findFirst().orElse(null);
if (sinkToBeProcessed != null) {
sinksToBeProcessed.add(sinkToBeProcessed);
}
}));
return sinksToBeProcessed;
}
Aggregations