blob: 8ef929ba61941e26faf3b78698442139b04ea6ba [file] [log] [blame]
/*
* Copyright 2017-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.ui.impl;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.ElementId;
import org.onosproject.net.HostId;
import org.onosproject.net.Link;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.criteria.PortCriterion;
import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
import org.onosproject.net.intent.FlowRuleIntent;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.intent.OpticalConnectivityIntent;
import org.onosproject.net.intent.ProtectionEndpointIntent;
import org.onosproject.net.link.LinkService;
import org.onosproject.ui.impl.topo.util.ServicesBundle;
import org.onosproject.ui.impl.topo.util.TrafficLink;
import org.onosproject.ui.impl.topo.util.TrafficLink.StatsType;
import org.onosproject.ui.impl.topo.util.TrafficLinkMap;
import org.onosproject.ui.topo.AbstractTopoMonitor;
import org.onosproject.ui.topo.DeviceHighlight;
import org.onosproject.ui.topo.Highlights;
import org.onosproject.ui.topo.HostHighlight;
import org.onosproject.ui.topo.LinkHighlight.Flavor;
import org.onosproject.ui.topo.Mod;
import org.onosproject.ui.topo.NodeHighlight;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.stream.Collectors;
import static org.onosproject.net.MarkerResource.marker;
import static org.onosproject.ui.impl.ProtectedIntentMonitor.ProtectedMode.IDLE;
import static org.onosproject.ui.impl.ProtectedIntentMonitor.ProtectedMode.SELECTED_INTENT;
/**
* Encapsulates the behavior of monitoring protected intents.
*/
//TODO refactor duplicated methods from here and the TrafficMonitor to AbstractTopoMonitor
public class ProtectedIntentMonitor extends AbstractTopoMonitor {
private static final Logger log =
LoggerFactory.getLogger(ProtectedIntentMonitor.class);
private static final String PRIMARY_PATH_TAG = "protection1";
private static final String PROT_PRIMARY = "protPrimary";
private static final String PROT_BACKUP = "protBackup";
private static final Mod MOD_PROT_PRIMARY = new Mod(PROT_PRIMARY);
private static final Set<Mod> PROTECTED_MOD_PRIMARY_SET =
ImmutableSet.of(MOD_PROT_PRIMARY);
private static final Mod MOD_PROT_BACKUP = new Mod(PROT_BACKUP);
private static final Set<Mod> PROTECTED_MOD_BACKUP_SET =
ImmutableSet.of(MOD_PROT_BACKUP);
/**
* Designates the different modes of operation.
*/
public enum ProtectedMode {
IDLE,
SELECTED_INTENT
}
private final long trafficPeriod;
private final ServicesBundle services;
private final TopologyViewMessageHandler msgHandler;
private final Timer timer = new Timer("topo-protected-intents");
private TimerTask trafficTask = null;
private ProtectedMode mode = ProtectedMode.IDLE;
private Intent selectedIntent = null;
/**
* Constructs a protected intent monitor.
*
* @param trafficPeriod traffic task period in ms
* @param services bundle of services
* @param msgHandler our message handler
*/
public ProtectedIntentMonitor(long trafficPeriod, ServicesBundle services,
TopologyViewMessageHandler msgHandler) {
this.trafficPeriod = trafficPeriod;
this.services = services;
this.msgHandler = msgHandler;
}
// =======================================================================
// === API ===
// TODO: move this out to the "h2h/multi-intent app"
/**
* Monitor for protected intent data to be sent back to the web client,
* for the given intent.
*
* @param intent the intent to monitor
*/
public synchronized void monitor(Intent intent) {
log.debug("monitor intent: {}", intent.id());
selectedIntent = intent;
mode = SELECTED_INTENT;
scheduleTask();
sendSelectedIntents();
}
/**
* Stop all traffic monitoring.
*/
public synchronized void stopMonitoring() {
log.debug("STOP monitoring");
if (mode != IDLE) {
sendClearAll();
}
}
// =======================================================================
// === Helper methods ===
private void sendClearAll() {
clearAll();
sendClearHighlights();
}
private void clearAll() {
this.mode = IDLE;
clearSelection();
cancelTask();
}
private void clearSelection() {
selectedIntent = null;
}
//TODO duplicate and can be brought in abstract upper class.
private synchronized void scheduleTask() {
if (trafficTask == null) {
log.debug("Starting up background protected intent task...");
trafficTask = new TrafficUpdateTask();
timer.schedule(trafficTask, trafficPeriod, trafficPeriod);
} else {
log.debug("(protected intent task already running)");
}
}
private synchronized void cancelTask() {
if (trafficTask != null) {
trafficTask.cancel();
trafficTask = null;
}
}
private void sendSelectedIntents() {
log.debug("sendSelectedIntents: {}", selectedIntent);
msgHandler.sendHighlights(protectedIntentHighlights());
}
private void sendClearHighlights() {
log.debug("sendClearHighlights");
msgHandler.sendHighlights(new Highlights());
}
// =======================================================================
// === Generate messages in JSON object node format
private Highlights protectedIntentHighlights() {
Highlights highlights = new Highlights();
TrafficLinkMap linkMap = new TrafficLinkMap();
IntentService intentService = services.intent();
if (selectedIntent != null) {
List<Intent> installables = intentService.getInstallableIntents(selectedIntent.key());
if (installables != null) {
ProtectionEndpointIntent ep1 = installables.stream()
.filter(ProtectionEndpointIntent.class::isInstance)
.map(ProtectionEndpointIntent.class::cast)
.findFirst().orElse(null);
ProtectionEndpointIntent ep2 = installables.stream()
.filter(ii -> !ii.equals(ep1))
.filter(ProtectionEndpointIntent.class::isInstance)
.map(ProtectionEndpointIntent.class::cast)
.findFirst().orElse(null);
if (ep1 == null || ep2 == null) {
log.warn("Selected Intent {} didn't have 2 protection endpoints",
selectedIntent.key());
stopMonitoring();
return highlights;
}
Set<Link> primary = new LinkedHashSet<>();
Set<Link> backup = new LinkedHashSet<>();
Map<Boolean, List<FlowRuleIntent>> transits = installables.stream()
.filter(FlowRuleIntent.class::isInstance)
.map(FlowRuleIntent.class::cast)
// only consider fwd links so that ants march in one direction
// TODO: didn't help need further investigation.
//.filter(i -> !i.resources().contains(marker("rev")))
.collect(Collectors.groupingBy(this::isPrimary));
// walk primary
ConnectPoint primHead = ep1.description().paths().get(0).output().connectPoint();
ConnectPoint primTail = ep2.description().paths().get(0).output().connectPoint();
List<FlowRuleIntent> primTransit = transits.getOrDefault(true, ImmutableList.of());
populateLinks(primary, primHead, primTail, primTransit);
// walk backup
ConnectPoint backHead = ep1.description().paths().get(1).output().connectPoint();
ConnectPoint backTail = ep2.description().paths().get(1).output().connectPoint();
List<FlowRuleIntent> backTransit = transits.getOrDefault(false, ImmutableList.of());
populateLinks(backup, backHead, backTail, backTransit);
// Add packet to optical links
if (!usingBackup(primary)) {
primary.addAll(protectedIntentMultiLayer(primHead, primTail));
}
backup.addAll(protectedIntentMultiLayer(backHead, backTail));
boolean isOptical = selectedIntent instanceof OpticalConnectivityIntent;
//last parameter (traffic) signals if the link is highlighted with ants or solid line
//Flavor is swapped so green is primary path.
if (usingBackup(primary)) {
//the backup becomes in use so we have a dotted line
processLinks(linkMap, backup, Flavor.PRIMARY_HIGHLIGHT,
isOptical, true, PROTECTED_MOD_BACKUP_SET);
} else {
processLinks(linkMap, primary, Flavor.PRIMARY_HIGHLIGHT,
isOptical, true, PROTECTED_MOD_PRIMARY_SET);
processLinks(linkMap, backup, Flavor.SECONDARY_HIGHLIGHT,
isOptical, false, PROTECTED_MOD_BACKUP_SET);
}
updateHighlights(highlights, primary);
updateHighlights(highlights, backup);
colorLinks(highlights, linkMap);
highlights.subdueAllElse(Highlights.Amount.MINIMALLY);
} else {
log.debug("Selected Intent has no installable intents");
}
} else {
log.debug("Selected Intent is null");
}
return highlights;
}
/**
* Returns the packet to optical mapping given a head and tail of a protection path.
*
* @param head head of path
* @param tail tail of path
*/
private Set<Link> protectedIntentMultiLayer(ConnectPoint head, ConnectPoint tail) {
List<Link> links = new LinkedList<>();
LinkService linkService = services.link();
IntentService intentService = services.intent();
// Ingress cross connect link
links.addAll(
linkService.getEgressLinks(head).stream()
.filter(l -> l.type() == Link.Type.OPTICAL)
.collect(Collectors.toList())
);
// Egress cross connect link
links.addAll(
linkService.getIngressLinks(tail).stream()
.filter(l -> l.type() == Link.Type.OPTICAL)
.collect(Collectors.toList())
);
// The protected intent does not rely on a multi-layer mapping
if (links.size() != 2) {
return Collections.emptySet();
}
// Expected head and tail of optical circuit (not connectivity!) intent
ConnectPoint ocHead = links.get(0).dst();
ConnectPoint ocTail = links.get(1).src();
// Optical connectivity
// FIXME: assumes that transponder (OTN device) is a one-to-one mapping
// We need to track the multi-layer aspects better
intentService.getIntents().forEach(intent -> {
if (intent instanceof OpticalConnectivityIntent) {
OpticalConnectivityIntent ocIntent = (OpticalConnectivityIntent) intent;
if (ocHead.deviceId().equals(ocIntent.getSrc().deviceId()) &&
ocTail.deviceId().equals(ocIntent.getDst().deviceId())) {
intentService.getInstallableIntents(ocIntent.key()).forEach(i -> {
if (i instanceof FlowRuleIntent) {
FlowRuleIntent fr = (FlowRuleIntent) i;
links.addAll(linkResources(fr));
}
});
}
}
});
return new LinkedHashSet<>(links);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/**
* Populate Links along the primary/backup path.
*
* @param links link collection to populate [output]
* @param head head-end of primary/backup path
* @param tail tail-end of primary/backup path
* @param transit Intents if any
*/
private void populateLinks(Set<Link> links,
ConnectPoint head,
ConnectPoint tail,
List<FlowRuleIntent> transit) {
// find first hop link
Link first = transit.stream()
// search for Link head -> transit Intent head
// as first candidate of 1st hop Link
.flatMap(fri -> fri.flowRules().stream())
.map(fr ->
// find first input port from FlowRule
Optional.ofNullable(fr.selector().getCriterion(Criterion.Type.IN_PORT))
.filter(PortCriterion.class::isInstance)
.map(PortCriterion.class::cast)
.map(PortCriterion::port)
.map(pn -> new ConnectPoint(fr.deviceId(), pn))
.orElse(null)
).filter(Objects::nonNull)
.map(dst -> services.link().getLink(head, dst))
.filter(Objects::nonNull)
.findFirst()
// if there isn't one probably 1 hop to the tail
.orElse(services.link().getLink(head, tail));
// add first link
if (first != null) {
links.add(first);
}
// add links in the middle if any
transit.forEach(fri -> links.addAll(linkResources(fri)));
// add last hop if any
Lists.reverse(transit).stream()
// search for Link transit Intent tail -> tail
// as candidate of last hop Link
.flatMap(fri -> ImmutableList.copyOf(fri.flowRules()).reverse().stream())
.map(fr ->
// find first output port from FlowRule
fr.treatment().allInstructions().stream()
.filter(OutputInstruction.class::isInstance).findFirst()
.map(OutputInstruction.class::cast)
.map(OutputInstruction::port)
.map(pn -> new ConnectPoint(fr.deviceId(), pn))
.orElse(null)
).filter(Objects::nonNull)
.map(src -> services.link().getLink(src, tail))
.filter(Objects::nonNull)
.findFirst()
.ifPresent(links::add);
}
/**
* Returns true if specified intent is marked with primary marker resource.
*
* @param intent to test
* @return true if it is an Intent taking part of primary transit path
*/
private boolean isPrimary(Intent intent) {
return intent.resources()
.contains(marker(PRIMARY_PATH_TAG));
}
// returns true if the backup path is the one where the traffic is currently flowing
private boolean usingBackup(Set<Link> primary) {
Set<Link> activeLinks = Sets.newHashSet(services.link().getActiveLinks());
return primary.isEmpty() || !activeLinks.containsAll(primary);
}
private void updateHighlights(Highlights highlights, Iterable<Link> links) {
for (Link link : links) {
ensureNodePresent(highlights, link.src().elementId());
ensureNodePresent(highlights, link.dst().elementId());
}
}
//TODO duplicate and can be brought in abstract upper class.
private void ensureNodePresent(Highlights highlights, ElementId eid) {
String id = eid.toString();
NodeHighlight nh = highlights.getNode(id);
if (nh == null) {
if (eid instanceof DeviceId) {
nh = new DeviceHighlight(id);
highlights.add((DeviceHighlight) nh);
} else if (eid instanceof HostId) {
nh = new HostHighlight(id);
highlights.add((HostHighlight) nh);
}
}
}
private void processLinks(TrafficLinkMap linkMap, Iterable<Link> links,
Flavor flavor, boolean isOptical,
boolean showTraffic, Set<Mod> mods) {
if (links != null) {
for (Link link : links) {
TrafficLink tlink = linkMap.add(link);
tlink.tagFlavor(flavor);
tlink.optical(isOptical);
if (showTraffic) {
tlink.antMarch(true);
}
tlink.tagMods(mods);
}
}
}
//TODO duplicate and can be brought in abstract upper class.
private void colorLinks(Highlights highlights, TrafficLinkMap linkMap) {
for (TrafficLink tlink : linkMap.biLinks()) {
highlights.add(tlink.highlight(StatsType.TAGGED));
}
}
//TODO duplicate and can be brought in abstract upper class.
// Extracts links from the specified flow rule intent resources
private Collection<Link> linkResources(Intent installable) {
ImmutableList.Builder<Link> builder = ImmutableList.builder();
installable.resources().stream().filter(r -> r instanceof Link)
.forEach(r -> builder.add((Link) r));
return builder.build();
}
// =======================================================================
// === Background Task
// Provides periodic update of traffic information to the client
private class TrafficUpdateTask extends TimerTask {
@Override
public void run() {
try {
switch (mode) {
case SELECTED_INTENT:
sendSelectedIntents();
break;
default:
// RELATED_INTENTS and IDLE modes should never invoke
// the background task, but if they do, they have
// nothing to do
break;
}
} catch (Exception e) {
log.warn("Unable to process protected intent task due to {}", e.getMessage());
log.warn("Boom!", e);
}
}
}
}