blob: 1d5127d1697909885908f2e6cfca8242034815c4 [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.ui.impl;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.ElementId;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.Link;
import org.onosproject.net.PortNumber;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
import org.onosproject.net.intent.FlowObjectiveIntent;
import org.onosproject.net.intent.FlowRuleIntent;
import org.onosproject.net.intent.HostToHostIntent;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.LinkCollectionIntent;
import org.onosproject.net.intent.OpticalConnectivityIntent;
import org.onosproject.net.intent.OpticalPathIntent;
import org.onosproject.net.intent.PathIntent;
import org.onosproject.net.link.LinkService;
import org.onosproject.ui.impl.topo.util.IntentSelection;
import org.onosproject.ui.impl.topo.util.ServicesBundle;
import org.onosproject.ui.impl.topo.util.TopoIntentFilter;
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.DeviceHighlight;
import org.onosproject.ui.topo.Highlights;
import org.onosproject.ui.topo.Highlights.Amount;
import org.onosproject.ui.topo.HostHighlight;
import org.onosproject.ui.topo.LinkHighlight.Flavor;
import org.onosproject.ui.topo.NodeHighlight;
import org.onosproject.ui.topo.NodeSelection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.RELATED_INTENTS;
import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.SELECTED_INTENT;
/**
* Encapsulates the behavior of monitoring specific traffic patterns.
*/
public class TrafficMonitor extends TrafficMonitorBase {
private static final Logger log =
LoggerFactory.getLogger(TrafficMonitor.class);
private final TopologyViewMessageHandler msgHandler;
private final TopoIntentFilter intentFilter;
private NodeSelection selectedNodes = null;
private IntentSelection selectedIntents = null;
/**
* Constructs a traffic monitor.
*
* @param trafficPeriod traffic task period in ms
* @param servicesBundle bundle of services
* @param msgHandler our message handler
*/
public TrafficMonitor(long trafficPeriod, ServicesBundle servicesBundle,
TopologyViewMessageHandler msgHandler) {
super(trafficPeriod, servicesBundle);
this.msgHandler = msgHandler;
intentFilter = new TopoIntentFilter(servicesBundle);
}
// =======================================================================
// === API ===
// monitor(Mode) is now implemented in the super class
/**
* Monitor for traffic data to be sent back to the web client, under
* the given mode, using the given selection of devices and hosts.
* In the case of "device link flows", this causes a background traffic
* task to be scheduled to repeatedly compute and transmit the appropriate
* traffic data to the client. In the case of "related intents", no
* repeating task is scheduled.
* <p>
* The monitoring mode is expected to be one of:
* <ul>
* <li>DEV_LINK_FLOWS</li>
* <li>RELATED_INTENTS</li>
* </ul>
*
* @param mode monitoring mode
* @param nodeSelection how to select a node
*/
public synchronized void monitor(Mode mode, NodeSelection nodeSelection) {
log.debug("monitor: {} -- {}", mode, nodeSelection);
this.mode = mode;
this.selectedNodes = nodeSelection;
switch (mode) {
case DEV_LINK_FLOWS:
// only care about devices (not hosts)
if (selectedNodes.devicesWithHover().isEmpty()) {
clearAll();
} else {
scheduleTask();
sendDeviceLinkFlows();
}
break;
case RELATED_INTENTS:
if (selectedNodes.none()) {
clearAll();
} else {
selectedIntents = new IntentSelection(selectedNodes, intentFilter);
if (selectedIntents.none()) {
clearAll();
} else {
sendSelectedIntents();
}
}
break;
default:
log.debug("Unexpected call to monitor({}, {})", mode, nodeSelection);
clearAll();
break;
}
}
// TODO: move this out to the "h2h/multi-intent app"
/**
* Monitor for traffic 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());
selectedNodes = null;
selectedIntents = new IntentSelection(intent);
mode = SELECTED_INTENT;
scheduleTask();
sendSelectedIntentTraffic();
}
/**
* Selects the next intent in the select group (if there is one),
* and sends highlighting data back to the web client to display
* which path is selected.
*/
public synchronized void selectNextIntent() {
if (selectedIntents != null) {
selectedIntents.next();
sendSelectedIntents();
if (mode == SELECTED_INTENT) {
mode = RELATED_INTENTS;
}
}
}
/**
* Selects the previous intent in the select group (if there is one),
* and sends highlighting data back to the web client to display
* which path is selected.
*/
public synchronized void selectPreviousIntent() {
if (selectedIntents != null) {
selectedIntents.prev();
sendSelectedIntents();
if (mode == SELECTED_INTENT) {
mode = RELATED_INTENTS;
}
}
}
/**
* Resends selected intent traffic data. This is called, for example,
* when the system detects an intent update happened.
*/
public synchronized void pokeIntent() {
if (mode == SELECTED_INTENT) {
sendSelectedIntentTraffic();
}
}
// =======================================================================
// === Abstract method implementations ===
@Override
protected void sendAllFlowTraffic() {
log.debug("sendAllFlowTraffic");
msgHandler.sendHighlights(trafficSummary(StatsType.FLOW_STATS));
}
@Override
protected void sendAllPortTrafficBits() {
log.debug("sendAllPortTrafficBits");
msgHandler.sendHighlights(trafficSummary(StatsType.PORT_STATS));
}
@Override
protected void sendAllPortTrafficPackets() {
log.debug("sendAllPortTrafficPackets");
msgHandler.sendHighlights(trafficSummary(StatsType.PORT_PACKET_STATS));
}
@Override
protected void sendDeviceLinkFlows() {
log.debug("sendDeviceLinkFlows: {}", selectedNodes);
msgHandler.sendHighlights(deviceLinkFlows());
}
@Override
protected void sendSelectedIntentTraffic() {
log.debug("sendSelectedIntentTraffic: {}", selectedIntents);
msgHandler.sendHighlights(intentTraffic());
}
@Override
protected void sendClearHighlights() {
log.debug("sendClearHighlights");
msgHandler.sendHighlights(new Highlights());
}
@Override
protected void clearSelection() {
selectedNodes = null;
selectedIntents = null;
}
private void sendSelectedIntents() {
log.debug("sendSelectedIntents: {}", selectedIntents);
msgHandler.sendHighlights(intentGroup());
}
// =======================================================================
// === Generate messages in JSON object node format
// NOTE: trafficSummary(StatsType) => Highlights
// has been moved to the superclass
// create highlights for links, showing flows for selected devices.
private Highlights deviceLinkFlows() {
Highlights highlights = new Highlights();
if (selectedNodes != null && !selectedNodes.devicesWithHover().isEmpty()) {
// capture flow counts on bilinks
TrafficLinkMap linkMap = new TrafficLinkMap();
for (Device device : selectedNodes.devicesWithHover()) {
Map<Link, Integer> counts = getLinkFlowCounts(device.id());
for (Link link : counts.keySet()) {
TrafficLink tlink = linkMap.add(link);
tlink.addFlows(counts.get(link));
}
}
// now report on our collated links
for (TrafficLink tlink : linkMap.biLinks()) {
highlights.add(tlink.highlight(StatsType.FLOW_COUNT));
}
}
return highlights;
}
private Highlights intentGroup() {
Highlights highlights = new Highlights();
if (selectedIntents != null && !selectedIntents.none()) {
// If 'all' intents are selected, they will all have primary
// highlighting; otherwise, the specifically selected intent will
// have primary highlighting, and the remainder will have secondary
// highlighting.
Set<Intent> primary;
Set<Intent> secondary;
int count = selectedIntents.size();
Set<Intent> allBut = new HashSet<>(selectedIntents.intents());
Intent current;
if (selectedIntents.all()) {
primary = allBut;
secondary = Collections.emptySet();
log.debug("Highlight all intents ({})", count);
} else {
current = selectedIntents.current();
primary = new HashSet<>();
primary.add(current);
allBut.remove(current);
secondary = allBut;
log.debug("Highlight intent: {} ([{}] of {})",
current.id(), selectedIntents.index(), count);
}
highlightIntentLinks(highlights, primary, secondary);
}
return highlights;
}
private Highlights intentTraffic() {
Highlights highlights = new Highlights();
if (selectedIntents != null && selectedIntents.single()) {
Intent current = selectedIntents.current();
Set<Intent> primary = new HashSet<>();
primary.add(current);
log.debug("Highlight traffic for intent: {} ([{}] of {})",
current.id(), selectedIntents.index(), selectedIntents.size());
highlightIntentLinksWithTraffic(highlights, primary);
highlights.subdueAllElse(Amount.MINIMALLY);
}
return highlights;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Counts all flow entries that egress on the links of the given device.
private Map<Link, Integer> getLinkFlowCounts(DeviceId deviceId) {
// get the flows for the device
List<FlowEntry> entries = new ArrayList<>();
for (FlowEntry flowEntry : services.flow().getFlowEntries(deviceId)) {
entries.add(flowEntry);
}
// get egress links from device, and include edge links
Set<Link> links = new HashSet<>(services.link()
.getDeviceEgressLinks(deviceId));
Set<Host> hosts = services.host().getConnectedHosts(deviceId);
if (hosts != null) {
for (Host host : hosts) {
links.add(createEdgeLink(host, false));
}
}
// compile flow counts per link
Map<Link, Integer> counts = new HashMap<>();
for (Link link : links) {
counts.put(link, getEgressFlows(link, entries));
}
return counts;
}
// Counts all entries that egress on the link source port.
private int getEgressFlows(Link link, List<FlowEntry> entries) {
int count = 0;
PortNumber out = link.src().port();
for (FlowEntry entry : entries) {
TrafficTreatment treatment = entry.treatment();
for (Instruction instruction : treatment.allInstructions()) {
if (instruction.type() == Instruction.Type.OUTPUT &&
((OutputInstruction) instruction).port().equals(out)) {
count++;
}
}
}
return count;
}
private void highlightIntentLinks(Highlights highlights,
Set<Intent> primary, Set<Intent> secondary) {
TrafficLinkMap linkMap = new TrafficLinkMap();
// NOTE: highlight secondary first, then primary, so that links shared
// by intents are colored correctly ("last man wins")
createTrafficLinks(highlights, linkMap, secondary, Flavor.SECONDARY_HIGHLIGHT, false);
createTrafficLinks(highlights, linkMap, primary, Flavor.PRIMARY_HIGHLIGHT, false);
colorLinks(highlights, linkMap);
}
private void highlightIntentLinksWithTraffic(Highlights highlights,
Set<Intent> primary) {
TrafficLinkMap linkMap = new TrafficLinkMap();
createTrafficLinks(highlights, linkMap, primary, Flavor.PRIMARY_HIGHLIGHT, true);
colorLinks(highlights, linkMap);
}
private void createTrafficLinks(Highlights highlights,
TrafficLinkMap linkMap, Set<Intent> intents,
Flavor flavor, boolean showTraffic) {
for (Intent intent : intents) {
List<Intent> installables = services.intent()
.getInstallableIntents(intent.key());
Iterable<Link> links = null;
if (installables != null) {
for (Intent installable : installables) {
if (installable instanceof PathIntent) {
links = ((PathIntent) installable).path().links();
} else if (installable instanceof FlowRuleIntent) {
Collection<Link> l = new ArrayList<>();
l.addAll(linkResources(installable));
// Add cross connect links
if (intent instanceof OpticalConnectivityIntent) {
OpticalConnectivityIntent ocIntent = (OpticalConnectivityIntent) intent;
LinkService linkService = services.link();
DeviceService deviceService = services.device();
l.addAll(linkService.getDeviceIngressLinks(ocIntent.getSrc().deviceId()).stream()
.filter(i ->
deviceService.getDevice(i.src().deviceId()).type() == Device.Type.SWITCH)
.collect(Collectors.toList()));
l.addAll(linkService.getDeviceEgressLinks(ocIntent.getDst().deviceId()).stream()
.filter(e ->
deviceService.getDevice(e.dst().deviceId()).type() == Device.Type.SWITCH)
.collect(Collectors.toList()));
}
links = l;
} else if (installable instanceof FlowObjectiveIntent) {
links = linkResources(installable);
} else if (installable instanceof LinkCollectionIntent) {
links = ((LinkCollectionIntent) installable).links();
} else if (installable instanceof OpticalPathIntent) {
links = ((OpticalPathIntent) installable).path().links();
}
if (links == null) {
links = Lists.newArrayList();
}
links = addEdgeLinksIfNeeded(intent, Lists.newArrayList(links));
boolean isOptical = intent instanceof OpticalConnectivityIntent;
processLinks(linkMap, links, flavor, isOptical, showTraffic);
updateHighlights(highlights, links);
}
}
}
}
private Iterable<Link> addEdgeLinksIfNeeded(Intent parentIntent,
Collection<Link> links) {
if (parentIntent instanceof HostToHostIntent) {
links = new HashSet<>(links);
HostToHostIntent h2h = (HostToHostIntent) parentIntent;
Host h1 = services.host().getHost(h2h.one());
Host h2 = services.host().getHost(h2h.two());
links.add(createEdgeLink(h1, true));
links.add(createEdgeLink(h2, true));
}
return links;
}
private void updateHighlights(Highlights highlights, Iterable<Link> links) {
for (Link link : links) {
ensureNodePresent(highlights, link.src().elementId());
ensureNodePresent(highlights, link.dst().elementId());
}
}
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);
}
}
}
// 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();
}
private void processLinks(TrafficLinkMap linkMap, Iterable<Link> links,
Flavor flavor, boolean isOptical,
boolean showTraffic) {
if (links != null) {
for (Link link : links) {
TrafficLink tlink = linkMap.add(link);
tlink.tagFlavor(flavor);
tlink.optical(isOptical);
if (showTraffic) {
tlink.addLoad(getLinkFlowLoad(link));
tlink.antMarch(true);
}
}
}
}
private void colorLinks(Highlights highlights, TrafficLinkMap linkMap) {
for (TrafficLink tlink : linkMap.biLinks()) {
highlights.add(tlink.highlight(StatsType.TAGGED));
}
}
}