Simon Hunt

ONOS-1479 -- GUI - augmenting topology view for extensibility: WIP.

- Major refactoring of TopologyViewMessageHandler and related classes.

Change-Id: I920f7f9f7317f3987a9a8da35ac086e9f8cab8d3
Showing 22 changed files with 2178 additions and 742 deletions
/*
* Copyright 2015 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.topo;
/**
* Partial implementation of the types of highlight to apply to topology
* elements.
*/
public abstract class AbstractHighlight {
private final TopoElementType type;
private final String elementId;
public AbstractHighlight(TopoElementType type, String elementId) {
this.type = type;
this.elementId = elementId;
}
public TopoElementType type() {
return type;
}
public String elementId() {
return elementId;
}
}
/*
* Copyright 2015 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.topo;
/**
* Denotes the types of highlight to apply to a link.
*/
public class DeviceHighlight extends AbstractHighlight {
public DeviceHighlight(String deviceId) {
super(TopoElementType.DEVICE, deviceId);
}
}
/*
* Copyright 2015 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.topo;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* Encapsulates highlights to be applied to the topology view, such as
* highlighting links, displaying link labels, perhaps even decorating
* nodes with badges, etc.
*/
public class Highlights {
private static final DecimalFormat DF0 = new DecimalFormat("#,###");
private final Set<DeviceHighlight> devices = new HashSet<>();
private final Set<HostHighlight> hosts = new HashSet<>();
private final Set<LinkHighlight> links = new HashSet<>();
public Highlights add(DeviceHighlight d) {
devices.add(d);
return this;
}
public Highlights add(HostHighlight h) {
hosts.add(h);
return this;
}
public Highlights add(LinkHighlight lh) {
links.add(lh);
return this;
}
public Set<DeviceHighlight> devices() {
return Collections.unmodifiableSet(devices);
}
public Set<HostHighlight> hosts() {
return Collections.unmodifiableSet(hosts);
}
public Set<LinkHighlight> links() {
return Collections.unmodifiableSet(links);
}
}
/*
* Copyright 2015 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.topo;
/**
* Denotes the types of highlight to apply to a link.
*/
public class HostHighlight extends AbstractHighlight {
public HostHighlight(String hostId) {
super(TopoElementType.HOST, hostId);
}
}
/*
* Copyright 2015 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.topo;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Denotes the highlighting to be applied to a link.
* {@link Flavor} is a closed set of NO-, PRIMARY-, or SECONDARY- highlighting.
* {@link Mod} is an open ended set of additional modifications (CSS classes)
* to apply. Note that {@link #MOD_OPTICAL} and {@link #MOD_ANIMATED} are
* pre-defined mods.
* Label text may be set, which will also be displayed on the link.
*/
public class LinkHighlight extends AbstractHighlight {
private static final String PLAIN = "plain";
private static final String PRIMARY = "primary";
private static final String SECONDARY = "secondary";
private static final String EMPTY = "";
private static final String SPACE = " ";
private final Flavor flavor;
private final Set<Mod> mods = new TreeSet<>();
private String label = EMPTY;
/**
* Constructs a link highlight entity.
*
* @param linkId the link identifier
* @param flavor the highlight flavor
*/
public LinkHighlight(String linkId, Flavor flavor) {
super(TopoElementType.LINK, linkId);
this.flavor = checkNotNull(flavor);
}
/**
* Adds a highlighting modification to this link highlight.
*
* @param mod mod to be added
* @return self, for chaining
*/
public LinkHighlight addMod(Mod mod) {
mods.add(checkNotNull(mod));
return this;
}
/**
* Adds a label to be displayed on the link.
*
* @param label the label text
* @return self, for chaining
*/
public LinkHighlight setLabel(String label) {
this.label = label == null ? EMPTY : label;
return this;
}
/**
* Returns the highlight flavor.
*
* @return highlight flavor
*/
public Flavor flavor() {
return flavor;
}
/**
* Returns the highlight modifications.
*
* @return highlight modifications
*/
public Set<Mod> mods() {
return Collections.unmodifiableSet(mods);
}
/**
* Generates the CSS classes string from the {@link #flavor} and
* any optional {@link #mods}.
*
* @return CSS classes string
*/
public String cssClasses() {
StringBuilder sb = new StringBuilder(flavor.toString());
mods.forEach(m -> sb.append(SPACE).append(m));
return sb.toString();
}
/**
* Returns the label text.
*
* @return label text
*/
public String label() {
return label;
}
/**
* Link highlighting flavor.
*/
public enum Flavor {
NO_HIGHLIGHT(PLAIN),
PRIMARY_HIGHLIGHT(PRIMARY),
SECONDARY_HIGHLIGHT(SECONDARY);
private String cssName;
Flavor(String s) {
cssName = s;
}
@Override
public String toString() {
return cssName;
}
}
/**
* Link highlighting modification.
* <p>
* Note that this translates to a CSS class name that is applied to
* the link in the Topology UI.
*/
public static final class Mod implements Comparable<Mod> {
private final String modId;
public Mod(String modId) {
this.modId = checkNotNull(modId);
}
@Override
public String toString() {
return modId;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Mod mod = (Mod) o;
return modId.equals(mod.modId);
}
@Override
public int hashCode() {
return modId.hashCode();
}
@Override
public int compareTo(Mod o) {
return this.modId.compareTo(o.modId);
}
}
/**
* Denotes a link to be tagged as an optical link.
*/
public static final Mod MOD_OPTICAL = new Mod("optical");
/**
* Denotes a link to be tagged with animated traffic ("marching ants").
*/
public static final Mod MOD_ANIMATED = new Mod("animated");
}
/*
* Copyright 2015 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.topo;
/**
* The topology element types to which a highlight can be applied.
*/
public enum TopoElementType {
DEVICE, HOST, LINK
}
......@@ -24,7 +24,8 @@ import org.onosproject.net.LinkKey;
import org.onosproject.net.link.LinkService;
import org.onosproject.ui.RequestHandler;
import org.onosproject.ui.UiMessageHandler;
import org.onosproject.ui.impl.TopologyViewMessageHandlerBase.BiLink;
import org.onosproject.ui.impl.topo.BiLink;
import org.onosproject.ui.impl.topo.TopoUtils;
import org.onosproject.ui.table.TableModel;
import org.onosproject.ui.table.TableRequestHandler;
import org.onosproject.ui.table.cell.ConnectPointFormatter;
......@@ -33,13 +34,14 @@ import org.onosproject.ui.table.cell.EnumFormatter;
import java.util.Collection;
import java.util.Map;
import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.addLink;
/**
* Message handler for link view related messages.
*/
public class LinkViewMessageHandler extends UiMessageHandler {
private static final String A_BOTH_B = "A &harr; B";
private static final String A_SINGLE_B = "A &rarr; B";
private static final String LINK_DATA_REQ = "linkDataRequest";
private static final String LINK_DATA_RESP = "linkDataResponse";
private static final String LINKS = "links";
......@@ -94,38 +96,39 @@ public class LinkViewMessageHandler extends UiMessageHandler {
// First consolidate all uni-directional links into two-directional ones.
Map<LinkKey, BiLink> biLinks = Maps.newHashMap();
ls.getLinks().forEach(link -> addLink(biLinks, link));
ls.getLinks().forEach(link -> TopoUtils.addLink(biLinks, link));
// Now scan over all bi-links and produce table rows from them.
biLinks.values().forEach(biLink -> populateRow(tm.addRow(), biLink));
}
private void populateRow(TableModel.Row row, BiLink biLink) {
row.cell(ONE, biLink.one.src())
.cell(TWO, biLink.one.dst())
row.cell(ONE, biLink.one().src())
.cell(TWO, biLink.one().dst())
.cell(TYPE, linkType(biLink))
.cell(STATE, linkState(biLink))
.cell(DIRECTION, linkDir(biLink))
.cell(DURABLE, biLink.one.isDurable());
.cell(DURABLE, biLink.one().isDurable());
}
private String linkType(BiLink link) {
StringBuilder sb = new StringBuilder();
sb.append(link.one.type());
if (link.two != null && link.two.type() != link.one.type()) {
sb.append(" / ").append(link.two.type());
sb.append(link.one().type());
if (link.two() != null && link.two().type() != link.one().type()) {
sb.append(" / ").append(link.two().type());
}
return sb.toString();
}
private String linkState(BiLink link) {
return (link.one.state() == Link.State.ACTIVE ||
link.two.state() == Link.State.ACTIVE) ?
return (link.one().state() == Link.State.ACTIVE ||
link.two().state() == Link.State.ACTIVE) ?
ICON_ID_ONLINE : ICON_ID_OFFLINE;
}
private String linkDir(BiLink link) {
return link.two != null ? "A &harr; B" : "A &rarr; B";
return link.two() != null ? A_BOTH_B : A_SINGLE_B;
}
}
}
......
......@@ -48,7 +48,6 @@ import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostListener;
import org.onosproject.net.intent.HostToHostIntent;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentEvent;
import org.onosproject.net.intent.IntentListener;
import org.onosproject.net.intent.MultiPointToSinglePointIntent;
......@@ -57,6 +56,9 @@ import org.onosproject.net.link.LinkListener;
import org.onosproject.ui.JsonUtils;
import org.onosproject.ui.RequestHandler;
import org.onosproject.ui.UiConnection;
import org.onosproject.ui.impl.TrafficMonitorObject.Mode;
import org.onosproject.ui.impl.topo.NodeSelection;
import org.onosproject.ui.topo.Highlights;
import org.onosproject.ui.topo.PropertyPanel;
import java.util.ArrayList;
......@@ -70,7 +72,6 @@ import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
......@@ -117,8 +118,6 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
// fields
private static final String ID = "id";
private static final String IDS = "ids";
private static final String HOVER = "hover";
private static final String DEVICE = "device";
private static final String HOST = "host";
private static final String CLASS = "class";
......@@ -132,14 +131,12 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
private static final String NAMES = "names";
private static final String ACTIVATE = "activate";
private static final String DEACTIVATE = "deactivate";
private static final String PRIMARY = "primary";
private static final String SECONDARY = "secondary";
private static final String APP_ID = "org.onosproject.gui";
private static final long TRAFFIC_FREQUENCY = 5000;
private static final long SUMMARY_FREQUENCY = 30000;
private static final long TRAFFIC_PERIOD = 5000;
private static final long SUMMARY_PERIOD = 30000;
private static final Comparator<? super ControllerNode> NODE_COMPARATOR =
(o1, o2) -> o1.id().toString().compareTo(o2.id().toString());
......@@ -165,31 +162,21 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
private final ExecutorService msgSender =
newSingleThreadExecutor(groupedThreads("onos/gui", "msg-sender"));
private TopoOverlayCache overlayCache;
private TrafficMonitorObject tmo;
private TimerTask trafficTask = null;
private TrafficEvent trafficEvent = null;
private TopoOverlayCache overlayCache;
private TimerTask summaryTask = null;
private boolean summaryRunning = false;
private boolean listenersRemoved = false;
private TopologyViewIntentFilter intentFilter;
// Current selection context
private Set<Host> selectedHosts;
private Set<Device> selectedDevices;
private List<Intent> selectedIntents;
private int currentIntentIndex = -1;
@Override
public void init(UiConnection connection, ServiceDirectory directory) {
super.init(connection, directory);
intentFilter = new TopologyViewIntentFilter(intentService, deviceService,
hostService, linkService);
appId = directory.get(CoreService.class).registerApplication(APP_ID);
tmo = new TrafficMonitorObject(TRAFFIC_PERIOD, servicesBundle, this);
}
@Override
......@@ -214,18 +201,18 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
new UpdateMeta(),
new EqMasters(),
// TODO: implement "showHighlights" event (replaces "showTraffic")
// TODO: migrate traffic related to separate app
new AddHostIntent(),
new AddMultiSourceIntent(),
new ReqAllFlowTraffic(),
new ReqAllPortTraffic(),
new ReqDevLinkFlows(),
new ReqRelatedIntents(),
new ReqNextIntent(),
new ReqPrevIntent(),
new ReqSelectedIntentTraffic(),
new ReqAllFlowTraffic(),
new ReqAllPortTraffic(),
new ReqDevLinkFlows(),
new CancelTraffic()
);
}
......@@ -288,7 +275,7 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
@Override
public void process(long sid, ObjectNode payload) {
stopSummaryMonitoring();
stopTrafficMonitoring();
tmo.stop();
}
}
......@@ -390,6 +377,9 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
}
}
// ========= -----------------------------------------------------------------
// === TODO: move traffic related classes to traffic app
private final class AddHostIntent extends RequestHandler {
......@@ -410,7 +400,7 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
.build();
intentService.submit(intent);
startMonitoringIntent(intent);
tmo.monitor(intent);
}
}
......@@ -443,116 +433,90 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
.build();
intentService.submit(intent);
startMonitoringIntent(intent);
tmo.monitor(intent);
}
}
private final class ReqRelatedIntents extends RequestHandler {
private ReqRelatedIntents() {
super(REQ_RELATED_INTENTS);
// ========= -----------------------------------------------------------------
private final class ReqAllFlowTraffic extends RequestHandler {
private ReqAllFlowTraffic() {
super(REQ_ALL_FLOW_TRAFFIC);
}
@Override
public void process(long sid, ObjectNode payload) {
// Cancel any other traffic monitoring mode.
stopTrafficMonitoring();
if (!payload.has(IDS)) {
return;
}
// Get the set of selected hosts and their intents.
ArrayNode ids = (ArrayNode) payload.path(IDS);
selectedHosts = getHosts(ids);
selectedDevices = getDevices(ids);
selectedIntents = intentFilter.findPathIntents(
selectedHosts, selectedDevices, intentService.getIntents());
currentIntentIndex = -1;
if (haveSelectedIntents()) {
// Send a message to highlight all links of all monitored intents.
sendMessage(trafficMessage(new TrafficClass(PRIMARY, selectedIntents)));
}
// TODO: Re-introduce once the client click vs hover gesture stuff is sorted out.
// String hover = string(payload, "hover");
// if (!isNullOrEmpty(hover)) {
// // If there is a hover node, include it in the selection and find intents.
// processHoverExtendedSelection(sid, hover);
// }
tmo.monitor(Mode.ALL_FLOW_TRAFFIC);
}
}
private final class ReqNextIntent extends RequestHandler {
private ReqNextIntent() {
super(REQ_NEXT_INTENT);
private final class ReqAllPortTraffic extends RequestHandler {
private ReqAllPortTraffic() {
super(REQ_ALL_PORT_TRAFFIC);
}
@Override
public void process(long sid, ObjectNode payload) {
stopTrafficMonitoring();
requestAnotherRelatedIntent(+1);
tmo.monitor(Mode.ALL_PORT_TRAFFIC);
}
}
private final class ReqPrevIntent extends RequestHandler {
private ReqPrevIntent() {
super(REQ_PREV_INTENT);
private final class ReqDevLinkFlows extends RequestHandler {
private ReqDevLinkFlows() {
super(REQ_DEV_LINK_FLOWS);
}
@Override
public void process(long sid, ObjectNode payload) {
stopTrafficMonitoring();
requestAnotherRelatedIntent(-1);
NodeSelection nodeSelection =
new NodeSelection(payload, deviceService, hostService);
tmo.monitor(Mode.DEV_LINK_FLOWS, nodeSelection);
}
}
private final class ReqSelectedIntentTraffic extends RequestHandler {
private ReqSelectedIntentTraffic() {
super(REQ_SEL_INTENT_TRAFFIC);
private final class ReqRelatedIntents extends RequestHandler {
private ReqRelatedIntents() {
super(REQ_RELATED_INTENTS);
}
@Override
public void process(long sid, ObjectNode payload) {
trafficEvent = new TrafficEvent(TrafficEvent.Type.SEL_INTENT, payload);
requestSelectedIntentTraffic();
startTrafficMonitoring();
NodeSelection nodeSelection =
new NodeSelection(payload, deviceService, hostService);
tmo.monitor(Mode.RELATED_INTENTS, nodeSelection);
}
}
private final class ReqAllFlowTraffic extends RequestHandler {
private ReqAllFlowTraffic() {
super(REQ_ALL_FLOW_TRAFFIC);
private final class ReqNextIntent extends RequestHandler {
private ReqNextIntent() {
super(REQ_NEXT_INTENT);
}
@Override
public void process(long sid, ObjectNode payload) {
trafficEvent = new TrafficEvent(TrafficEvent.Type.ALL_FLOW_TRAFFIC, payload);
requestAllFlowTraffic();
tmo.selectNextIntent();
}
}
private final class ReqAllPortTraffic extends RequestHandler {
private ReqAllPortTraffic() {
super(REQ_ALL_PORT_TRAFFIC);
private final class ReqPrevIntent extends RequestHandler {
private ReqPrevIntent() {
super(REQ_PREV_INTENT);
}
@Override
public void process(long sid, ObjectNode payload) {
trafficEvent = new TrafficEvent(TrafficEvent.Type.ALL_PORT_TRAFFIC, payload);
requestAllPortTraffic();
tmo.selectPreviousIntent();
}
}
private final class ReqDevLinkFlows extends RequestHandler {
private ReqDevLinkFlows() {
super(REQ_DEV_LINK_FLOWS);
private final class ReqSelectedIntentTraffic extends RequestHandler {
private ReqSelectedIntentTraffic() {
super(REQ_SEL_INTENT_TRAFFIC);
}
@Override
public void process(long sid, ObjectNode payload) {
trafficEvent = new TrafficEvent(TrafficEvent.Type.DEV_LINK_FLOWS, payload);
requestDeviceLinkFlows(payload);
tmo.monitor(Mode.SEL_INTENT);
}
}
......@@ -563,14 +527,16 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
@Override
public void process(long sid, ObjectNode payload) {
selectedIntents = null;
sendMessage(trafficMessage());
stopTrafficMonitoring();
tmo.stop();
}
}
//=======================================================================
// Converts highlights to JSON format and sends the message to the client
protected void sendHighlights(Highlights highlights) {
sendMessage(JsonUtils.envelope(SHOW_HIGHLIGHTS, json(highlights)));
}
// Sends the specified data to the client.
protected synchronized void sendMessage(ObjectNode data) {
......@@ -591,7 +557,7 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
private void cancelAllRequests() {
stopSummaryMonitoring();
stopTrafficMonitoring();
tmo.stop();
}
// Sends all controller nodes to the client as node-added messages.
......@@ -641,18 +607,6 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
}
}
private synchronized void startMonitoringIntent(Intent intent) {
selectedHosts = new HashSet<>();
selectedDevices = new HashSet<>();
selectedIntents = new ArrayList<>();
selectedIntents.add(intent);
currentIntentIndex = -1;
requestAnotherRelatedIntent(+1);
requestSelectedIntentTraffic();
}
private Set<ConnectPoint> getHostLocations(Set<HostId> hostIds) {
Set<ConnectPoint> points = new HashSet<>();
for (HostId hostId : hostIds) {
......@@ -675,121 +629,10 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
}
private synchronized void startTrafficMonitoring() {
stopTrafficMonitoring();
trafficTask = new TrafficMonitor();
timer.schedule(trafficTask, TRAFFIC_FREQUENCY, TRAFFIC_FREQUENCY);
}
private synchronized void stopTrafficMonitoring() {
if (trafficTask != null) {
trafficTask.cancel();
trafficTask = null;
}
}
// Subscribes for flow traffic messages.
private synchronized void requestAllFlowTraffic() {
startTrafficMonitoring();
sendMessage(trafficSummaryMessage(StatsType.FLOW));
}
// Subscribes for port traffic messages.
private synchronized void requestAllPortTraffic() {
startTrafficMonitoring();
sendMessage(trafficSummaryMessage(StatsType.PORT));
}
private void requestDeviceLinkFlows(ObjectNode payload) {
startTrafficMonitoring();
// Get the set of selected hosts and their intents.
ArrayNode ids = (ArrayNode) payload.path(IDS);
Set<Host> hosts = new HashSet<>();
Set<Device> devices = getDevices(ids);
// If there is a hover node, include it in the hosts and find intents.
String hover = JsonUtils.string(payload, HOVER);
if (!isNullOrEmpty(hover)) {
addHover(hosts, devices, hover);
}
sendMessage(flowSummaryMessage(devices));
}
private boolean haveSelectedIntents() {
return selectedIntents != null && !selectedIntents.isEmpty();
}
// Processes the selection extended with hovered item to segregate items
// into primary (those including the hover) vs secondary highlights.
private void processHoverExtendedSelection(long sid, String hover) {
Set<Host> hoverSelHosts = new HashSet<>(selectedHosts);
Set<Device> hoverSelDevices = new HashSet<>(selectedDevices);
addHover(hoverSelHosts, hoverSelDevices, hover);
List<Intent> primary = selectedIntents == null ? new ArrayList<>() :
intentFilter.findPathIntents(hoverSelHosts, hoverSelDevices,
selectedIntents);
Set<Intent> secondary = new HashSet<>(selectedIntents);
secondary.removeAll(primary);
// Send a message to highlight all links of all monitored intents.
sendMessage(trafficMessage(new TrafficClass(PRIMARY, primary),
new TrafficClass(SECONDARY, secondary)));
}
// Requests next or previous related intent.
private void requestAnotherRelatedIntent(int offset) {
if (haveSelectedIntents()) {
currentIntentIndex = currentIntentIndex + offset;
if (currentIntentIndex < 0) {
currentIntentIndex = selectedIntents.size() - 1;
} else if (currentIntentIndex >= selectedIntents.size()) {
currentIntentIndex = 0;
}
sendSelectedIntent();
}
}
// Sends traffic information on the related intents with the currently
// selected intent highlighted.
private void sendSelectedIntent() {
Intent selectedIntent = selectedIntents.get(currentIntentIndex);
log.debug("Requested next intent {}", selectedIntent.id());
Set<Intent> primary = new HashSet<>();
primary.add(selectedIntent);
Set<Intent> secondary = new HashSet<>(selectedIntents);
secondary.remove(selectedIntent);
// Send a message to highlight all links of the selected intent.
sendMessage(trafficMessage(new TrafficClass(PRIMARY, primary),
new TrafficClass(SECONDARY, secondary)));
}
// Requests monitoring of traffic for the selected intent.
private void requestSelectedIntentTraffic() {
if (haveSelectedIntents()) {
if (currentIntentIndex < 0) {
currentIntentIndex = 0;
}
Intent selectedIntent = selectedIntents.get(currentIntentIndex);
log.debug("Requested traffic for selected {}", selectedIntent.id());
Set<Intent> primary = new HashSet<>();
primary.add(selectedIntent);
// Send a message to highlight all links of the selected intent.
sendMessage(trafficMessage(new TrafficClass(PRIMARY, primary, true)));
}
}
private synchronized void startSummaryMonitoring() {
stopSummaryMonitoring();
summaryTask = new SummaryMonitor();
timer.schedule(summaryTask, SUMMARY_FREQUENCY, SUMMARY_FREQUENCY);
timer.schedule(summaryTask, SUMMARY_PERIOD, SUMMARY_PERIOD);
summaryRunning = true;
}
......@@ -883,9 +726,7 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
private class InternalIntentListener implements IntentListener {
@Override
public void event(IntentEvent event) {
if (trafficTask != null) {
msgSender.execute(TopologyViewMessageHandler.this::requestSelectedIntentTraffic);
}
msgSender.execute(tmo::pokeIntent);
eventAccummulator.add(event);
}
}
......@@ -898,51 +739,8 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
}
}
// encapsulate
private static class TrafficEvent {
enum Type {
ALL_FLOW_TRAFFIC, ALL_PORT_TRAFFIC, DEV_LINK_FLOWS, SEL_INTENT
}
private final Type type;
private final ObjectNode payload;
TrafficEvent(Type type, ObjectNode payload) {
this.type = type;
this.payload = payload;
}
}
// Periodic update of the traffic information
private class TrafficMonitor extends TimerTask {
@Override
public void run() {
try {
if (trafficEvent != null) {
switch (trafficEvent.type) {
case ALL_FLOW_TRAFFIC:
requestAllFlowTraffic();
break;
case ALL_PORT_TRAFFIC:
requestAllPortTraffic();
break;
case DEV_LINK_FLOWS:
requestDeviceLinkFlows(trafficEvent.payload);
break;
case SEL_INTENT:
requestSelectedIntentTraffic();
break;
default:
// nothing to do
break;
}
}
} catch (Exception e) {
log.warn("Unable to handle traffic request due to {}", e.getMessage());
log.warn("Boom!", e);
}
}
}
// === SUMMARY MONITORING
// Periodic update of the summary information
private class SummaryMonitor extends TimerTask {
......@@ -967,7 +765,7 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
@Override
public void processItems(List<Event> items) {
// Start-of-Debugging
// Start-of-Debugging -- Keep in until ONOS-2572 is fixed for reals
long now = System.currentTimeMillis();
String me = this.toString();
String miniMe = me.replaceAll("^.*@", "me@");
......
......@@ -18,7 +18,6 @@ package org.onosproject.ui.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableList;
import org.onlab.osgi.ServiceDirectory;
import org.onlab.packet.IpAddress;
import org.onosproject.cluster.ClusterEvent;
......@@ -43,8 +42,6 @@ import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.HostLocation;
import org.onosproject.net.Link;
import org.onosproject.net.LinkKey;
import org.onosproject.net.NetworkResource;
import org.onosproject.net.PortNumber;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceService;
......@@ -55,29 +52,26 @@ import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostService;
import org.onosproject.net.intent.FlowRuleIntent;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentService;
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.LinkEvent;
import org.onosproject.net.link.LinkService;
import org.onosproject.net.provider.ProviderId;
import org.onosproject.net.statistic.Load;
import org.onosproject.net.statistic.StatisticService;
import org.onosproject.net.topology.Topology;
import org.onosproject.net.topology.TopologyService;
import org.onosproject.ui.JsonUtils;
import org.onosproject.ui.UiConnection;
import org.onosproject.ui.UiMessageHandler;
import org.onosproject.ui.impl.topo.ServicesBundle;
import org.onosproject.ui.topo.ButtonId;
import org.onosproject.ui.topo.DeviceHighlight;
import org.onosproject.ui.topo.Highlights;
import org.onosproject.ui.topo.HostHighlight;
import org.onosproject.ui.topo.LinkHighlight;
import org.onosproject.ui.topo.PropertyPanel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
......@@ -95,10 +89,6 @@ import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
import static org.onosproject.net.DeviceId.deviceId;
import static org.onosproject.net.HostId.hostId;
import static org.onosproject.net.LinkKey.linkKey;
import static org.onosproject.net.PortNumber.P0;
import static org.onosproject.net.PortNumber.portNumber;
import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
......@@ -106,8 +96,7 @@ import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.FLOW;
import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.PORT;
import static org.onosproject.ui.impl.topo.TopoUtils.compactLinkString;
import static org.onosproject.ui.topo.TopoConstants.CoreButtons;
import static org.onosproject.ui.topo.TopoConstants.Properties;
......@@ -121,24 +110,8 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
private static final ProviderId PID =
new ProviderId("core", "org.onosproject.core", true);
private static final String COMPACT = "%s/%s-%s/%s";
private static final String SHOW_HIGHLIGHTS = "showHighlights";
private static final double KILO = 1024;
private static final double MEGA = 1024 * KILO;
private static final double GIGA = 1024 * MEGA;
private static final String GBITS_UNIT = "Gb";
private static final String MBITS_UNIT = "Mb";
private static final String KBITS_UNIT = "Kb";
private static final String BITS_UNIT = "b";
private static final String GBYTES_UNIT = "GB";
private static final String MBYTES_UNIT = "MB";
private static final String KBYTES_UNIT = "KB";
private static final String BYTES_UNIT = "B";
//4 Kilo Bytes as threshold
private static final double BPS_THRESHOLD = 4 * KILO;
protected static final String SHOW_HIGHLIGHTS = "showHighlights";
protected ServiceDirectory directory;
protected ClusterService clusterService;
......@@ -153,9 +126,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
protected TopologyService topologyService;
protected TunnelService tunnelService;
protected enum StatsType {
FLOW, PORT
}
protected ServicesBundle servicesBundle;
private String version;
......@@ -187,6 +158,11 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
topologyService = directory.get(TopologyService.class);
tunnelService = directory.get(TunnelService.class);
servicesBundle = new ServicesBundle(intentService, deviceService,
hostService, linkService,
flowService,
flowStatsService, portStatsService);
String ver = directory.get(CoreService.class).version().toString();
version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
}
......@@ -232,64 +208,6 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
return JsonUtils.envelope("message", id, payload);
}
// Produces a set of all hosts listed in the specified JSON array.
protected Set<Host> getHosts(ArrayNode array) {
Set<Host> hosts = new HashSet<>();
if (array != null) {
for (JsonNode node : array) {
try {
addHost(hosts, hostId(node.asText()));
} catch (IllegalArgumentException e) {
log.debug("Skipping ID {}", node.asText());
}
}
}
return hosts;
}
// Adds the specified host to the set of hosts.
private void addHost(Set<Host> hosts, HostId hostId) {
Host host = hostService.getHost(hostId);
if (host != null) {
hosts.add(host);
}
}
// Produces a set of all devices listed in the specified JSON array.
protected Set<Device> getDevices(ArrayNode array) {
Set<Device> devices = new HashSet<>();
if (array != null) {
for (JsonNode node : array) {
try {
addDevice(devices, deviceId(node.asText()));
} catch (IllegalArgumentException e) {
log.debug("Skipping ID {}", node.asText());
}
}
}
return devices;
}
private void addDevice(Set<Device> devices, DeviceId deviceId) {
Device device = deviceService.getDevice(deviceId);
if (device != null) {
devices.add(device);
}
}
protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
try {
addHost(hosts, hostId(hover));
} catch (IllegalArgumentException e) {
try {
addDevice(devices, deviceId(hover));
} catch (IllegalArgumentException ne) {
log.debug("Skipping ID {}", hover);
}
}
}
// Produces a cluster instance message to the client.
protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
ControllerNode node = event.subject();
......@@ -445,6 +363,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
JsonUtils.node(payload, "memento"));
}
// -----------------------------------------------------------------------
// Create models of the data to return, that overlays can adjust / augment
......@@ -527,24 +446,24 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
return count;
}
// Counts all entries that egress on the given device links.
protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
// 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<>();
Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
Set<Host> hosts = hostService.getConnectedHosts(deviceId);
for (FlowEntry flowEntry : flowService.getFlowEntries(deviceId)) {
entries.add(flowEntry);
}
// Add all edge links to the set
// get egress links from device, and include edge links
Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
Set<Host> hosts = hostService.getConnectedHosts(deviceId);
if (hosts != null) {
for (Host host : hosts) {
links.add(new DefaultEdgeLink(host.providerId(),
new ConnectPoint(host.id(), P0),
host.location(), false));
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));
......@@ -553,7 +472,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
}
// Counts all entries that egress on the link source port.
private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
private int getEgressFlows(Link link, List<FlowEntry> entries) {
int count = 0;
PortNumber out = link.src().port();
for (FlowEntry entry : entries) {
......@@ -568,7 +487,6 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
return count;
}
// Returns host details response.
protected PropertyPanel hostDetails(HostId hostId, long sid) {
Host host = hostService.getHost(hostId);
......@@ -589,270 +507,98 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
.addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
.addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE));
// TODO: add button descriptors
// Potentially add button descriptors here
return pp;
}
// TODO: migrate to Traffic overlay
// Produces JSON message to trigger flow traffic overview visualization
protected ObjectNode trafficSummaryMessage(StatsType type) {
ObjectNode payload = objectNode();
ArrayNode paths = arrayNode();
payload.set("paths", paths);
ObjectNode pathNodeN = objectNode();
ArrayNode linksNodeN = arrayNode();
ArrayNode labelsN = arrayNode();
pathNodeN.put("class", "plain").put("traffic", false);
pathNodeN.set("links", linksNodeN);
pathNodeN.set("labels", labelsN);
paths.add(pathNodeN);
ObjectNode pathNodeT = objectNode();
ArrayNode linksNodeT = arrayNode();
ArrayNode labelsT = arrayNode();
pathNodeT.put("class", "secondary").put("traffic", true);
pathNodeT.set("links", linksNodeT);
pathNodeT.set("labels", labelsT);
paths.add(pathNodeT);
Map<LinkKey, BiLink> biLinks = consolidateLinks(linkService.getLinks());
addEdgeLinks(biLinks);
for (BiLink link : biLinks.values()) {
boolean bi = link.two != null;
if (type == FLOW) {
link.addLoad(getLinkLoad(link.one));
link.addLoad(bi ? getLinkLoad(link.two) : null);
} else if (type == PORT) {
//For a bi-directional traffic links, use
//the max link rate of either direction
link.addLoad(portStatsService.load(link.one.src()),
BPS_THRESHOLD,
portStatsService.load(link.one.dst()),
BPS_THRESHOLD);
}
if (link.hasTraffic) {
linksNodeT.add(compactLinkString(link.one));
labelsT.add(type == PORT ?
formatBitRate(link.rate) + "ps" :
formatBytes(link.bytes));
} else {
linksNodeN.add(compactLinkString(link.one));
labelsN.add("");
}
}
return JsonUtils.envelope(SHOW_HIGHLIGHTS, 0, payload);
}
// ----------------------------------------------------------------------
private Load getLinkLoad(Link link) {
if (link.src().elementId() instanceof DeviceId) {
return flowStatsService.load(link);
}
return null;
}
/**
* Transforms the given highlights model into a JSON message payload.
*
* @param highlights the model to transform
* @return JSON payload
*/
protected ObjectNode json(Highlights highlights) {
ObjectNode payload = objectNode();
private void addEdgeLinks(Map<LinkKey, BiLink> biLinks) {
hostService.getHosts().forEach(host -> {
addLink(biLinks, createEdgeLink(host, true));
addLink(biLinks, createEdgeLink(host, false));
});
}
ArrayNode devices = arrayNode();
ArrayNode hosts = arrayNode();
ArrayNode links = arrayNode();
private Map<LinkKey, BiLink> consolidateLinks(Iterable<Link> links) {
Map<LinkKey, BiLink> biLinks = new HashMap<>();
for (Link link : links) {
addLink(biLinks, link);
}
return biLinks;
}
payload.set("devices", devices);
payload.set("hosts", hosts);
payload.set("links", links);
// Produces JSON message to trigger flow overview visualization
protected ObjectNode flowSummaryMessage(Set<Device> devices) {
ObjectNode payload = objectNode();
ArrayNode paths = arrayNode();
payload.set("paths", paths);
highlights.devices().forEach(dh -> devices.add(json(dh)));
highlights.hosts().forEach(hh -> hosts.add(json(hh)));
jsonifyLinks(links, highlights.links());
for (Device device : devices) {
Map<Link, Integer> counts = getFlowCounts(device.id());
for (Link link : counts.keySet()) {
addLinkFlows(link, paths, counts.get(link));
}
}
return JsonUtils.envelope(SHOW_HIGHLIGHTS, 0, payload);
return payload;
}
private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
ObjectNode pathNode = objectNode();
ArrayNode linksNode = arrayNode();
ArrayNode labels = arrayNode();
boolean noFlows = count == null || count == 0;
pathNode.put("class", noFlows ? "secondary" : "primary");
pathNode.put("traffic", false);
pathNode.set("links", linksNode.add(compactLinkString(link)));
pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
(count == 1 ? " flow" : " flows"))));
paths.add(pathNode);
}
private void jsonifyLinks(ArrayNode links, Set<LinkHighlight> hilites) {
// a little more complicated than devices or hosts, since we are
// grouping the link highlights by CSS classes
// TODO: refactor this method (including client side) to use new format
// as a more compact representation of the data...
// * links:
// * "primary animated":
// * "link01" -> "label"
// * "link02" -> "label"
// * "secondary":
// * "link04" -> "label"
// * "link05" -> ""
// Produces JSON message to trigger traffic visualization
protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
ObjectNode payload = objectNode();
ArrayNode paths = arrayNode();
payload.set("paths", paths);
// Classify links based on their traffic traffic first...
Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
// Then separate the links into their respective classes and send them out.
Map<String, ObjectNode> pathNodes = new HashMap<>();
for (BiLink biLink : biLinks.values()) {
boolean hasTraffic = biLink.hasTraffic;
String tc = (biLink.classes() + (hasTraffic ? " animated" : "")).trim();
ObjectNode pathNode = pathNodes.get(tc);
if (pathNode == null) {
pathNode = objectNode()
.put("class", tc).put("traffic", hasTraffic);
pathNode.set("links", arrayNode());
pathNode.set("labels", arrayNode());
pathNodes.put(tc, pathNode);
paths.add(pathNode);
}
((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
}
return JsonUtils.envelope(SHOW_HIGHLIGHTS, 0, payload);
}
Map<String, List<String>> linkIdMap = new HashMap<>();
Map<String, List<String>> linkLabelMap = new HashMap<>();
List<String> ids;
List<String> labels;
// Classifies the link traffic according to the specified classes.
private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
Map<LinkKey, BiLink> biLinks = new HashMap<>();
for (TrafficClass trafficClass : trafficClasses) {
for (Intent intent : trafficClass.intents) {
boolean isOptical = intent instanceof OpticalConnectivityIntent;
List<Intent> installables = intentService.getInstallableIntents(intent.key());
if (installables != null) {
for (Intent installable : installables) {
String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
if (installable instanceof PathIntent) {
classifyLinks(type, biLinks, trafficClass.showTraffic,
((PathIntent) installable).path().links());
} else if (installable instanceof FlowRuleIntent) {
classifyLinks(type, biLinks, trafficClass.showTraffic,
linkResources(installable));
} else if (installable instanceof LinkCollectionIntent) {
classifyLinks(type, biLinks, trafficClass.showTraffic,
((LinkCollectionIntent) installable).links());
} else if (installable instanceof OpticalPathIntent) {
classifyLinks(type, biLinks, trafficClass.showTraffic,
((OpticalPathIntent) installable).path().links());
}
}
}
}
}
return biLinks;
}
for (LinkHighlight lh : hilites) {
String cls = lh.cssClasses();
ids = linkIdMap.get(cls);
labels = linkLabelMap.get(cls);
// Extracts links from the specified flow rule intent resources
private Collection<Link> linkResources(Intent installable) {
ImmutableList.Builder<Link> builder = ImmutableList.builder();
for (NetworkResource r : installable.resources()) {
if (r instanceof Link) {
builder.add((Link) r);
if (ids == null) { // labels will be null also
ids = new ArrayList<>();
linkIdMap.put(cls, ids);
labels = new ArrayList<>();
linkLabelMap.put(cls, labels);
}
}
return builder.build();
}
// Adds the link segments (path or tree) associated with the specified
// connectivity intent
private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
boolean showTraffic, Iterable<Link> links) {
if (links != null) {
for (Link link : links) {
BiLink biLink = addLink(biLinks, link);
if (showTraffic) {
biLink.addLoad(getLinkLoad(link));
}
biLink.addClass(type);
}
ids.add(lh.elementId());
labels.add(lh.label());
}
}
for (String cls : linkIdMap.keySet()) {
ObjectNode group = objectNode();
links.add(group);
static BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
LinkKey key = canonicalLinkKey(link);
BiLink biLink = biLinks.get(key);
if (biLink != null) {
biLink.setOther(link);
} else {
biLink = new BiLink(key, link);
biLinks.put(key, biLink);
}
return biLink;
}
group.put("class", cls);
// Poor-mans formatting to get the labels with byte counts looking nice.
private String formatBytes(long bytes) {
String unit;
double value;
if (bytes > GIGA) {
value = bytes / GIGA;
unit = GBYTES_UNIT;
} else if (bytes > MEGA) {
value = bytes / MEGA;
unit = MBYTES_UNIT;
} else if (bytes > KILO) {
value = bytes / KILO;
unit = KBYTES_UNIT;
} else {
value = bytes;
unit = BYTES_UNIT;
ArrayNode lnks = arrayNode();
ArrayNode labs = arrayNode();
group.set("links", lnks);
group.set("labels", labs);
linkIdMap.get(cls).forEach(lnks::add);
linkLabelMap.get(cls).forEach(labs::add);
}
DecimalFormat format = new DecimalFormat("#,###.##");
return format.format(value) + " " + unit;
}
// Poor-mans formatting to get the labels with bit rate looking nice.
private String formatBitRate(long bytes) {
String unit;
double value;
//Convert to bits
long bits = bytes * 8;
if (bits > GIGA) {
value = bits / GIGA;
unit = GBITS_UNIT;
// NOTE: temporary hack to clip rate at 10.0 Gbps
// Added for the CORD Fabric demo at ONS 2015
if (value > 10.0) {
value = 10.0;
}
} else if (bits > MEGA) {
value = bits / MEGA;
unit = MBITS_UNIT;
} else if (bits > KILO) {
value = bits / KILO;
unit = KBITS_UNIT;
} else {
value = bits;
unit = BITS_UNIT;
}
DecimalFormat format = new DecimalFormat("#,###.##");
return format.format(value) + " " + unit;
protected ObjectNode json(DeviceHighlight dh) {
// TODO: implement this once we know what a device highlight looks like
return objectNode();
}
// Produces compact string representation of a link.
private static String compactLinkString(Link link) {
return String.format(COMPACT, link.src().elementId(), link.src().port(),
link.dst().elementId(), link.dst().port());
protected ObjectNode json(HostHighlight hh) {
// TODO: implement this once we know what a host highlight looks like
return objectNode();
}
// translates the property panel into JSON, for returning to the client
......@@ -879,95 +625,4 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
return result;
}
// Produces canonical link key, i.e. one that will match link and its inverse.
static LinkKey canonicalLinkKey(Link link) {
String sn = link.src().elementId().toString();
String dn = link.dst().elementId().toString();
return sn.compareTo(dn) < 0 ?
linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
}
// Representation of link and its inverse and any traffic data.
static class BiLink {
public final LinkKey key;
public final Link one;
public Link two;
public boolean hasTraffic = false;
public long bytes = 0;
private Set<String> classes = new HashSet<>();
private long rate;
BiLink(LinkKey key, Link link) {
this.key = key;
this.one = link;
}
void setOther(Link link) {
this.two = link;
}
void addLoad(Load load) {
addLoad(load, 0);
}
void addLoad(Load load, double threshold) {
if (load != null) {
this.hasTraffic = hasTraffic || load.rate() > threshold;
this.bytes += load.latest();
this.rate += load.rate();
}
}
void addLoad(Load srcLinkLoad,
double srcLinkThreshold,
Load dstLinkLoad,
double dstLinkThreshold) {
//use the max of link load at source or destination
if (srcLinkLoad != null) {
this.hasTraffic = hasTraffic || srcLinkLoad.rate() > srcLinkThreshold;
this.bytes = srcLinkLoad.latest();
this.rate = srcLinkLoad.rate();
}
if (dstLinkLoad != null) {
if (dstLinkLoad.rate() > this.rate) {
this.bytes = dstLinkLoad.latest();
this.rate = dstLinkLoad.rate();
this.hasTraffic = hasTraffic || dstLinkLoad.rate() > dstLinkThreshold;
}
}
}
void addClass(String trafficClass) {
classes.add(trafficClass);
}
String classes() {
StringBuilder sb = new StringBuilder();
classes.forEach(c -> sb.append(c).append(" "));
return sb.toString().trim();
}
}
// TODO: move this to traffic overlay component
// Auxiliary carrier of data for requesting traffic message.
static class TrafficClass {
public final boolean showTraffic;
public final String type;
public final Iterable<Intent> intents;
TrafficClass(String type, Iterable<Intent> intents) {
this(type, intents, false);
}
TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
this.type = type;
this.intents = intents;
this.showTraffic = showTraffic;
}
}
}
......
/*
* Copyright 2015 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 org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.Link;
import org.onosproject.net.LinkKey;
import org.onosproject.net.PortNumber;
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.FlowRuleIntent;
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.statistic.Load;
import org.onosproject.ui.impl.topo.BiLink;
import org.onosproject.ui.impl.topo.IntentSelection;
import org.onosproject.ui.impl.topo.LinkStatsType;
import org.onosproject.ui.impl.topo.NodeSelection;
import org.onosproject.ui.impl.topo.ServicesBundle;
import org.onosproject.ui.impl.topo.TopoUtils;
import org.onosproject.ui.impl.topo.TopologyViewIntentFilter;
import org.onosproject.ui.impl.topo.TrafficClass;
import org.onosproject.ui.topo.Highlights;
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.Timer;
import java.util.TimerTask;
import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
import static org.onosproject.ui.impl.TrafficMonitorObject.Mode.IDLE;
import static org.onosproject.ui.impl.TrafficMonitorObject.Mode.SEL_INTENT;
import static org.onosproject.ui.topo.LinkHighlight.Flavor.PRIMARY_HIGHLIGHT;
import static org.onosproject.ui.topo.LinkHighlight.Flavor.SECONDARY_HIGHLIGHT;
/**
* Encapsulates the behavior of monitoring specific traffic patterns.
*/
public class TrafficMonitorObject {
// 4 Kilo Bytes as threshold
private static final double BPS_THRESHOLD = 4 * TopoUtils.KILO;
private static final Logger log =
LoggerFactory.getLogger(TrafficMonitorObject.class);
/**
* Designates the different modes of operation.
*/
public enum Mode {
IDLE,
ALL_FLOW_TRAFFIC,
ALL_PORT_TRAFFIC,
DEV_LINK_FLOWS,
RELATED_INTENTS,
SEL_INTENT
}
private final long trafficPeriod;
private final ServicesBundle servicesBundle;
private final TopologyViewMessageHandler messageHandler;
private final TopologyViewIntentFilter intentFilter;
private final Timer timer = new Timer("topo-traffic");
private TimerTask trafficTask = null;
private Mode mode = IDLE;
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 messageHandler our message handler
*/
public TrafficMonitorObject(long trafficPeriod,
ServicesBundle servicesBundle,
TopologyViewMessageHandler messageHandler) {
this.trafficPeriod = trafficPeriod;
this.servicesBundle = servicesBundle;
this.messageHandler = messageHandler;
intentFilter = new TopologyViewIntentFilter(servicesBundle);
}
// =======================================================================
// === API === // TODO: add javadocs
public synchronized void monitor(Mode mode) {
log.debug("monitor: {}", mode);
this.mode = mode;
switch (mode) {
case ALL_FLOW_TRAFFIC:
clearSelection();
scheduleTask();
sendAllFlowTraffic();
break;
case ALL_PORT_TRAFFIC:
clearSelection();
scheduleTask();
sendAllPortTraffic();
break;
case SEL_INTENT:
scheduleTask();
sendSelectedIntentTraffic();
break;
default:
log.debug("Unexpected call to monitor({})", mode);
clearAll();
break;
}
}
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.devices().isEmpty()) {
sendClearAll();
} else {
scheduleTask();
sendDeviceLinkFlows();
}
break;
case RELATED_INTENTS:
if (selectedNodes.none()) {
sendClearAll();
} else {
selectedIntents = new IntentSelection(selectedNodes, intentFilter);
if (selectedIntents.none()) {
sendClearAll();
} else {
sendSelectedIntents();
}
}
break;
default:
log.debug("Unexpected call to monitor({}, {})", mode, nodeSelection);
clearAll();
break;
}
}
public synchronized void monitor(Intent intent) {
log.debug("monitor intent: {}", intent.id());
selectedNodes = null;
selectedIntents = new IntentSelection(intent);
mode = SEL_INTENT;
scheduleTask();
sendSelectedIntentTraffic();
}
public synchronized void selectNextIntent() {
if (selectedIntents != null) {
selectedIntents.next();
sendSelectedIntents();
}
}
public synchronized void selectPreviousIntent() {
if (selectedIntents != null) {
selectedIntents.prev();
sendSelectedIntents();
}
}
public synchronized void pokeIntent() {
if (mode == SEL_INTENT) {
sendSelectedIntentTraffic();
}
}
public synchronized void stop() {
log.debug("STOP");
if (mode != IDLE) {
sendClearAll();
}
}
// =======================================================================
// === Helper methods ===
private void sendClearAll() {
clearAll();
sendClearHighlights();
}
private void clearAll() {
this.mode = IDLE;
clearSelection();
cancelTask();
}
private void clearSelection() {
selectedNodes = null;
selectedIntents = null;
}
private synchronized void scheduleTask() {
if (trafficTask == null) {
log.debug("Starting up background traffic task...");
trafficTask = new TrafficMonitor();
timer.schedule(trafficTask, trafficPeriod, trafficPeriod);
} else {
// TEMPORARY until we are sure this is working correctly
log.debug("(traffic task already running)");
}
}
private synchronized void cancelTask() {
if (trafficTask != null) {
trafficTask.cancel();
trafficTask = null;
}
}
// ---
private void sendAllFlowTraffic() {
log.debug("sendAllFlowTraffic");
sendHighlights(trafficSummary(LinkStatsType.FLOW_STATS));
}
private void sendAllPortTraffic() {
log.debug("sendAllPortTraffic");
sendHighlights(trafficSummary(LinkStatsType.PORT_STATS));
}
private void sendDeviceLinkFlows() {
log.debug("sendDeviceLinkFlows: {}", selectedNodes);
sendHighlights(deviceLinkFlows());
}
private void sendSelectedIntents() {
log.debug("sendSelectedIntents: {}", selectedIntents);
sendHighlights(intentGroup());
}
private void sendSelectedIntentTraffic() {
log.debug("sendSelectedIntentTraffic: {}", selectedIntents);
sendHighlights(intentTraffic());
}
private void sendClearHighlights() {
log.debug("sendClearHighlights");
sendHighlights(new Highlights());
}
private void sendHighlights(Highlights highlights) {
messageHandler.sendHighlights(highlights);
}
// =======================================================================
// === Generate messages in JSON object node format
private Highlights trafficSummary(LinkStatsType type) {
Highlights highlights = new Highlights();
// compile a set of bilinks (combining pairs of unidirectional links)
Map<LinkKey, BiLink> linkMap = new HashMap<>();
compileLinks(linkMap);
addEdgeLinks(linkMap);
for (BiLink blink : linkMap.values()) {
if (type == LinkStatsType.FLOW_STATS) {
attachFlowLoad(blink);
} else if (type == LinkStatsType.PORT_STATS) {
attachPortLoad(blink);
}
// we only want to report on links deemed to have traffic
if (blink.hasTraffic()) {
highlights.add(blink.generateHighlight(type));
}
}
return highlights;
}
// create highlights for links, showing flows for selected devices.
private Highlights deviceLinkFlows() {
Highlights highlights = new Highlights();
if (selectedNodes != null && !selectedNodes.devices().isEmpty()) {
// capture flow counts on bilinks
Map<LinkKey, BiLink> linkMap = new HashMap<>();
for (Device device : selectedNodes.devices()) {
Map<Link, Integer> counts = getLinkFlowCounts(device.id());
for (Link link : counts.keySet()) {
BiLink blink = TopoUtils.addLink(linkMap, link);
blink.addFlows(counts.get(link));
}
}
// now report on our collated links
for (BiLink blink : linkMap.values()) {
highlights.add(blink.generateHighlight(LinkStatsType.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);
}
TrafficClass tc1 = new TrafficClass(PRIMARY_HIGHLIGHT, primary);
TrafficClass tc2 = new TrafficClass(SECONDARY_HIGHLIGHT, secondary);
// classify primary links after secondary (last man wins)
highlightIntents(highlights, tc2, tc1);
}
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());
TrafficClass tc1 = new TrafficClass(PRIMARY_HIGHLIGHT, primary, true);
highlightIntents(highlights, tc1);
}
return highlights;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
private void compileLinks(Map<LinkKey, BiLink> linkMap) {
servicesBundle.linkService().getLinks()
.forEach(link -> TopoUtils.addLink(linkMap, link));
}
private void addEdgeLinks(Map<LinkKey, BiLink> biLinks) {
servicesBundle.hostService().getHosts().forEach(host -> {
TopoUtils.addLink(biLinks, createEdgeLink(host, true));
TopoUtils.addLink(biLinks, createEdgeLink(host, false));
});
}
private Load getLinkFlowLoad(Link link) {
if (link != null && link.src().elementId() instanceof DeviceId) {
return servicesBundle.flowStatsService().load(link);
}
return null;
}
private void attachFlowLoad(BiLink link) {
link.addLoad(getLinkFlowLoad(link.one()));
link.addLoad(getLinkFlowLoad(link.two()));
}
private void attachPortLoad(BiLink link) {
// For bi-directional traffic links, use
// the max link rate of either direction
// (we choose 'one' since we know that is never null)
Link one = link.one();
Load egressSrc = servicesBundle.portStatsService().load(one.src());
Load egressDst = servicesBundle.portStatsService().load(one.dst());
// link.addLoad(maxLoad(egressSrc, egressDst), BPS_THRESHOLD);
link.addLoad(maxLoad(egressSrc, egressDst), 10); // FIXME - debug only
}
private Load maxLoad(Load a, Load b) {
if (a == null) {
return b;
}
if (b == null) {
return a;
}
return a.rate() > b.rate() ? a : b;
}
// ---
// 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 : servicesBundle.flowService().getFlowEntries(deviceId)) {
entries.add(flowEntry);
}
// get egress links from device, and include edge links
Set<Link> links = new HashSet<>(servicesBundle.linkService().getDeviceEgressLinks(deviceId));
Set<Host> hosts = servicesBundle.hostService().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 highlightIntents(Highlights highlights,
TrafficClass... trafficClasses) {
Map<LinkKey, BiLink> linkMap = new HashMap<>();
for (TrafficClass trafficClass : trafficClasses) {
classifyLinkTraffic(linkMap, trafficClass);
}
for (BiLink blink : linkMap.values()) {
highlights.add(blink.generateHighlight(LinkStatsType.TAGGED));
}
}
private void classifyLinkTraffic(Map<LinkKey, BiLink> linkMap,
TrafficClass trafficClass) {
for (Intent intent : trafficClass.intents()) {
boolean isOptical = intent instanceof OpticalConnectivityIntent;
List<Intent> installables = servicesBundle.intentService()
.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) {
links = linkResources(installable);
} else if (installable instanceof LinkCollectionIntent) {
links = ((LinkCollectionIntent) installable).links();
} else if (installable instanceof OpticalPathIntent) {
links = ((OpticalPathIntent) installable).path().links();
}
classifyLinks(trafficClass, isOptical, linkMap, links);
}
}
}
}
private void classifyLinks(TrafficClass trafficClass, boolean isOptical,
Map<LinkKey, BiLink> linkMap,
Iterable<Link> links) {
if (links != null) {
for (Link link : links) {
BiLink blink = TopoUtils.addLink(linkMap, link);
if (trafficClass.showTraffic()) {
blink.addLoad(getLinkFlowLoad(link));
blink.setAntMarch(true);
}
blink.setOptical(isOptical);
blink.tagFlavor(trafficClass.flavor());
}
}
}
// 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 TrafficMonitor extends TimerTask {
@Override
public void run() {
try {
switch (mode) {
case ALL_FLOW_TRAFFIC:
sendAllFlowTraffic();
break;
case ALL_PORT_TRAFFIC:
sendAllPortTraffic();
break;
case DEV_LINK_FLOWS:
sendDeviceLinkFlows();
break;
case SEL_INTENT:
sendSelectedIntentTraffic();
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 traffic task due to {}", e.getMessage());
log.warn("Boom!", e);
}
}
}
}
/*
* Copyright 2015 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.topo;
import org.onosproject.net.Link;
import org.onosproject.net.LinkKey;
import org.onosproject.net.statistic.Load;
import org.onosproject.ui.topo.LinkHighlight;
import static org.onosproject.ui.topo.LinkHighlight.Flavor.NO_HIGHLIGHT;
import static org.onosproject.ui.topo.LinkHighlight.Flavor.PRIMARY_HIGHLIGHT;
import static org.onosproject.ui.topo.LinkHighlight.Flavor.SECONDARY_HIGHLIGHT;
/**
* Representation of a link and its inverse, and any associated traffic data.
* This class understands how to generate {@link LinkHighlight}s for sending
* back to the topology view.
*/
public class BiLink {
private static final String EMPTY = "";
private final LinkKey key;
private final Link one;
private Link two;
private boolean hasTraffic = false;
private long bytes = 0;
private long rate = 0;
private long flows = 0;
private boolean isOptical = false;
private LinkHighlight.Flavor taggedFlavor = NO_HIGHLIGHT;
private boolean antMarch = false;
/**
* Constructs a bilink for the given key and initial link.
*
* @param key canonical key for this bilink
* @param link first link
*/
public BiLink(LinkKey key, Link link) {
this.key = key;
this.one = link;
}
/**
* Sets the second link for this bilink.
*
* @param link second link
*/
public void setOther(Link link) {
this.two = link;
}
/**
* Sets the optical flag to the given value.
*
* @param b true if an optical link
*/
public void setOptical(boolean b) {
isOptical = b;
}
/**
* Sets the ant march flag to the given value.
*
* @param b true if marching ants required
*/
public void setAntMarch(boolean b) {
antMarch = b;
}
/**
* Tags this bilink with a link flavor to be used in visual rendering.
*
* @param flavor the flavor to tag
*/
public void tagFlavor(LinkHighlight.Flavor flavor) {
this.taggedFlavor = flavor;
}
/**
* Adds load statistics, marks the bilink as having traffic.
*
* @param load load to add
*/
public void addLoad(Load load) {
addLoad(load, 0);
}
/**
* Adds load statistics, marks the bilink as having traffic, if the
* load rate is greater than the given threshold.
*
* @param load load to add
* @param threshold threshold to register traffic
*/
public void addLoad(Load load, double threshold) {
if (load != null) {
this.hasTraffic = hasTraffic || load.rate() > threshold;
this.bytes += load.latest();
this.rate += load.rate();
}
}
/**
* Adds the given count of flows to this bilink.
*
* @param count count of flows
*/
public void addFlows(int count) {
this.flows += count;
}
/**
* Generates a link highlight entity, based on state of this bilink.
*
* @param type the type of statistics to use to interpret the data
* @return link highlight data for this bilink
*/
public LinkHighlight generateHighlight(LinkStatsType type) {
switch (type) {
case FLOW_COUNT:
return highlightForFlowCount(type);
case FLOW_STATS:
case PORT_STATS:
return highlightForStats(type);
case TAGGED:
return highlightForTagging(type);
default:
throw new IllegalStateException("unexpected case: " + type);
}
}
private LinkHighlight highlightForStats(LinkStatsType type) {
return new LinkHighlight(linkId(), SECONDARY_HIGHLIGHT)
.setLabel(generateLabel(type));
}
private LinkHighlight highlightForFlowCount(LinkStatsType type) {
LinkHighlight.Flavor flavor = flows() > 0 ?
PRIMARY_HIGHLIGHT : SECONDARY_HIGHLIGHT;
return new LinkHighlight(linkId(), flavor)
.setLabel(generateLabel(type));
}
private LinkHighlight highlightForTagging(LinkStatsType type) {
LinkHighlight hlite = new LinkHighlight(linkId(), flavor())
.setLabel(generateLabel(type));
if (isOptical()) {
hlite.addMod(LinkHighlight.MOD_OPTICAL);
}
if (isAntMarch()) {
hlite.addMod(LinkHighlight.MOD_ANIMATED);
}
return hlite;
}
// Generates a link identifier in the form that the Topology View on the
private String linkId() {
return TopoUtils.compactLinkString(one);
}
// Generates a string representation of the load, to be used as a label
private String generateLabel(LinkStatsType type) {
switch (type) {
case FLOW_COUNT:
return TopoUtils.formatFlows(flows());
case FLOW_STATS:
return TopoUtils.formatBytes(bytes());
case PORT_STATS:
return TopoUtils.formatBitRate(rate());
case TAGGED:
return hasTraffic() ? TopoUtils.formatBytes(bytes()) : EMPTY;
default:
return "?";
}
}
// === ----------------------------------------------------------------
// accessors
public LinkKey key() {
return key;
}
public Link one() {
return one;
}
public Link two() {
return two;
}
public boolean hasTraffic() {
return hasTraffic;
}
public boolean isOptical() {
return isOptical;
}
public boolean isAntMarch() {
return antMarch;
}
public LinkHighlight.Flavor flavor() {
return taggedFlavor;
}
public long bytes() {
return bytes;
}
public long rate() {
return rate;
}
public long flows() {
return flows;
}
}
/*
* Copyright 2015 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.topo;
import org.onosproject.net.intent.Intent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Encapsulates a selection of intents (paths) inferred from a selection
* of devices and/or hosts from the topology view.
*/
public class IntentSelection {
private static final int ALL = -1;
protected static final Logger log =
LoggerFactory.getLogger(IntentSelection.class);
private final NodeSelection nodes;
private final List<Intent> intents;
private int index = ALL;
/**
* Creates an intent selection group, based on selected nodes.
*
* @param nodes node selection
* @param filter intent filter
*/
public IntentSelection(NodeSelection nodes, TopologyViewIntentFilter filter) {
this.nodes = nodes;
intents = filter.findPathIntents(nodes.hosts(), nodes.devices());
}
/**
* Creates an intent selection group, for a single intent.
*
* @param intent the intent
*/
public IntentSelection(Intent intent) {
nodes = null;
intents = new ArrayList<>(1);
intents.add(intent);
index = 0;
}
/**
* Returns true if no intents are selected.
*
* @return true if nothing selected
*/
public boolean none() {
return intents.isEmpty();
}
/**
* Returns true if all intents in this select group are currently selected.
* This is the initial state, so that all intents are shown on the
* topology view with primary highlighting.
*
* @return true if all selected
*/
public boolean all() {
return index == ALL;
}
/**
* Returns true if there is a single intent in this select group, or if
* a specific intent has been marked (index != ALL).
*
* @return true if single intent marked
*/
public boolean single() {
return !all();
}
/**
* Returns the number of intents in this selection group.
*
* @return number of intents
*/
public int size() {
return intents.size();
}
/**
* Returns the index of the currently selected intent.
*
* @return the current index
*/
public int index() {
return index;
}
/**
* The list of intents in this selection group.
*
* @return list of intents
*/
public List<Intent> intents() {
return Collections.unmodifiableList(intents);
}
/**
* Marks and returns the next intent in this group. Note that the
* selection wraps around to the beginning again, if necessary.
*
* @return the next intent in the group
*/
public Intent next() {
index += 1;
if (index >= intents.size()) {
index = 0;
}
return intents.get(index);
}
/**
* Marks and returns the previous intent in this group. Note that the
* selection wraps around to the end again, if necessary.
*
* @return the previous intent in the group
*/
public Intent prev() {
index -= 1;
if (index < 0) {
index = intents.size() - 1;
}
return intents.get(index);
}
/**
* Returns the currently marked intent, or null if "all" intents
* are marked.
*
* @return the currently marked intent
*/
public Intent current() {
return all() ? null : intents.get(index);
}
@Override
public String toString() {
return "IntentSelection{" +
"nodes=" + nodes +
", #intents=" + intents.size() +
", index=" + index +
'}';
}
}
/*
* Copyright 2015 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.topo;
/**
* Designates type of stats to report on a highlighted link.
*/
public enum LinkStatsType {
/**
* Number of flows.
*/
FLOW_COUNT,
/**
* Number of bytes.
*/
FLOW_STATS,
/**
* Number of bits per second.
*/
PORT_STATS,
/**
* Custom tagged information.
*/
TAGGED
}
/*
* Copyright 2015 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.topo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.net.Device;
import org.onosproject.net.Host;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.host.HostService;
import org.onosproject.ui.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.onosproject.net.DeviceId.deviceId;
import static org.onosproject.net.HostId.hostId;
/**
* Encapsulates a selection of devices and/or hosts from the topology view.
*/
public class NodeSelection {
protected static final Logger log =
LoggerFactory.getLogger(NodeSelection.class);
private static final String IDS = "ids";
private static final String HOVER = "hover";
private final DeviceService deviceService;
private final HostService hostService;
private final Set<String> ids;
private final String hover;
private final Set<Device> devices = new HashSet<>();
private final Set<Host> hosts = new HashSet<>();
/**
* Creates a node selection entity, from the given payload, using the
* supplied device and host services.
*
* @param payload message payload
* @param deviceService device service
* @param hostService host service
*/
public NodeSelection(ObjectNode payload,
DeviceService deviceService,
HostService hostService) {
this.deviceService = deviceService;
this.hostService = hostService;
ids = extractIds(payload);
hover = extractHover(payload);
Set<String> unmatched = findDevices(ids);
unmatched = findHosts(unmatched);
if (unmatched.size() > 0) {
log.debug("Skipping unmatched IDs {}", unmatched);
}
if (!isNullOrEmpty(hover)) {
unmatched = new HashSet<>();
unmatched.add(hover);
unmatched = findDevices(unmatched);
unmatched = findHosts(unmatched);
if (unmatched.size() > 0) {
log.debug("Skipping unmatched HOVER {}", unmatched);
}
}
}
/**
* Returns a view of the selected devices.
*
* @return selected devices
*/
public Set<Device> devices() {
return Collections.unmodifiableSet(devices);
}
/**
* Returns a view of the selected hosts.
*
* @return selected hosts
*/
public Set<Host> hosts() {
return Collections.unmodifiableSet(hosts);
}
/**
* Returns true if nothing is selected.
*
* @return true if nothing selected
*/
public boolean none() {
return devices().size() == 0 && hosts().size() == 0;
}
@Override
public String toString() {
return "NodeSelection{" +
"ids=" + ids +
", hover='" + hover + '\'' +
", #devices=" + devices.size() +
", #hosts=" + hosts.size() +
'}';
}
// == helper methods
private Set<String> extractIds(ObjectNode payload) {
ArrayNode array = (ArrayNode) payload.path(IDS);
if (array == null || array.size() == 0) {
return Collections.emptySet();
}
Set<String> ids = new HashSet<>();
for (JsonNode node : array) {
ids.add(node.asText());
}
return ids;
}
private String extractHover(ObjectNode payload) {
return JsonUtils.string(payload, HOVER);
}
private Set<String> findDevices(Set<String> ids) {
Set<String> unmatched = new HashSet<>();
Device device;
for (String id : ids) {
try {
device = deviceService.getDevice(deviceId(id));
if (device != null) {
devices.add(device);
} else {
log.debug("Device with ID {} not found", id);
}
} catch (IllegalArgumentException e) {
unmatched.add(id);
}
}
return unmatched;
}
private Set<String> findHosts(Set<String> ids) {
Set<String> unmatched = new HashSet<>();
Host host;
for (String id : ids) {
try {
host = hostService.getHost(hostId(id));
if (host != null) {
hosts.add(host);
} else {
log.debug("Host with ID {} not found", id);
}
} catch (IllegalArgumentException e) {
unmatched.add(id);
}
}
return unmatched;
}
}
/*
* Copyright 2015 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.topo;
import org.onosproject.incubator.net.PortStatisticsService;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.host.HostService;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.link.LinkService;
import org.onosproject.net.statistic.StatisticService;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A bundle of services that the topology view requires to get its job done.
*/
public class ServicesBundle {
private final IntentService intentService;
private final DeviceService deviceService;
private final HostService hostService;
private final LinkService linkService;
private final FlowRuleService flowService;
private final StatisticService flowStatsService;
private final PortStatisticsService portStatsService;
/**
* Creates the services bundle.
* @param intentService intent service reference
* @param deviceService device service reference
* @param hostService host service reference
* @param linkService link service reference
* @param flowService flow service reference
* @param flowStatsService flow statistics service reference
* @param portStatsService port statistics service reference
*/
public ServicesBundle(IntentService intentService,
DeviceService deviceService,
HostService hostService,
LinkService linkService,
FlowRuleService flowService,
StatisticService flowStatsService,
PortStatisticsService portStatsService) {
this.intentService = checkNotNull(intentService);
this.deviceService = checkNotNull(deviceService);
this.hostService = checkNotNull(hostService);
this.linkService = checkNotNull(linkService);
this.flowService = checkNotNull(flowService);
this.flowStatsService = checkNotNull(flowStatsService);
this.portStatsService = checkNotNull(portStatsService);
}
public IntentService intentService() {
return intentService;
}
public DeviceService deviceService() {
return deviceService;
}
public HostService hostService() {
return hostService;
}
public LinkService linkService() {
return linkService;
}
public FlowRuleService flowService() {
return flowService;
}
public StatisticService flowStatsService() {
return flowStatsService;
}
public PortStatisticsService portStatsService() {
return portStatsService;
}
}
/*
* Copyright 2015 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.topo;
import org.onosproject.net.Link;
import org.onosproject.net.LinkKey;
import java.text.DecimalFormat;
import java.util.Map;
import static org.onosproject.net.LinkKey.linkKey;
/**
* Utility methods for helping out with the topology view.
*/
public final class TopoUtils {
public static final double KILO = 1024;
public static final double MEGA = 1024 * KILO;
public static final double GIGA = 1024 * MEGA;
public static final String GBITS_UNIT = "Gb";
public static final String MBITS_UNIT = "Mb";
public static final String KBITS_UNIT = "Kb";
public static final String BITS_UNIT = "b";
public static final String GBYTES_UNIT = "GB";
public static final String MBYTES_UNIT = "MB";
public static final String KBYTES_UNIT = "KB";
public static final String BYTES_UNIT = "B";
private static final DecimalFormat DF2 = new DecimalFormat("#,###.##");
private static final String COMPACT = "%s/%s-%s/%s";
private static final String EMPTY = "";
private static final String SPACE = " ";
private static final String PER_SEC = "ps";
private static final String FLOW = "flow";
private static final String FLOWS = "flows";
// non-instantiable
private TopoUtils() { }
/**
* Returns a compact identity for the given link, in the form
* used to identify links in the Topology View on the client.
*
* @param link link
* @return compact link identity
*/
public static String compactLinkString(Link link) {
return String.format(COMPACT, link.src().elementId(), link.src().port(),
link.dst().elementId(), link.dst().port());
}
/**
* Produces a canonical link key, that is, one that will match both a link
* and its inverse.
*
* @param link the link
* @return canonical key
*/
public static LinkKey canonicalLinkKey(Link link) {
String sn = link.src().elementId().toString();
String dn = link.dst().elementId().toString();
return sn.compareTo(dn) < 0 ?
linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
}
/**
* Returns human readable count of bytes, to be displayed as a label.
*
* @param bytes number of bytes
* @return formatted byte count
*/
public static String formatBytes(long bytes) {
String unit;
double value;
if (bytes > GIGA) {
value = bytes / GIGA;
unit = GBYTES_UNIT;
} else if (bytes > MEGA) {
value = bytes / MEGA;
unit = MBYTES_UNIT;
} else if (bytes > KILO) {
value = bytes / KILO;
unit = KBYTES_UNIT;
} else {
value = bytes;
unit = BYTES_UNIT;
}
return DF2.format(value) + SPACE + unit;
}
/**
* Returns human readable bit rate, to be displayed as a label.
*
* @param bytes bytes per second
* @return formatted bits per second
*/
public static String formatBitRate(long bytes) {
String unit;
double value;
//Convert to bits
long bits = bytes * 8;
if (bits > GIGA) {
value = bits / GIGA;
unit = GBITS_UNIT;
// NOTE: temporary hack to clip rate at 10.0 Gbps
// Added for the CORD Fabric demo at ONS 2015
// TODO: provide a more elegant solution to this issue
if (value > 10.0) {
value = 10.0;
}
} else if (bits > MEGA) {
value = bits / MEGA;
unit = MBITS_UNIT;
} else if (bits > KILO) {
value = bits / KILO;
unit = KBITS_UNIT;
} else {
value = bits;
unit = BITS_UNIT;
}
return DF2.format(value) + SPACE + unit + PER_SEC;
}
/**
* Returns human readable flow count, to be displayed as a label.
*
* @param flows number of flows
* @return formatted flow count
*/
public static String formatFlows(long flows) {
if (flows < 1) {
return EMPTY;
}
return String.valueOf(flows) + SPACE + (flows > 1 ? FLOWS : FLOW);
}
/**
* Creates a new biLink with the supplied link (and adds it to the map),
* or attaches the link to an existing biLink (which already has the
* peer link).
*
* @param linkMap map of biLinks
* @param link the link to add
* @return the biLink to which the link was added
*/
// creates a new biLink with supplied link, or attaches link to the
// existing biLink (which already has its peer link)
public static BiLink addLink(Map<LinkKey, BiLink> linkMap, Link link) {
LinkKey key = TopoUtils.canonicalLinkKey(link);
BiLink biLink = linkMap.get(key);
if (biLink != null) {
biLink.setOther(link);
} else {
biLink = new BiLink(key, link);
linkMap.put(key, biLink);
}
return biLink;
}
}
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.ui.impl;
package org.onosproject.ui.impl.topo;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Device;
......@@ -56,32 +56,35 @@ public class TopologyViewIntentFilter {
private final LinkService linkService;
/**
* Crreates an intent filter.
* Creates an intent filter.
*
* @param intentService intent service reference
* @param deviceService device service reference
* @param hostService host service reference
* @param linkService link service reference
* @param services service references bundle
*/
TopologyViewIntentFilter(IntentService intentService, DeviceService deviceService,
HostService hostService, LinkService linkService) {
this.intentService = intentService;
this.deviceService = deviceService;
this.hostService = hostService;
this.linkService = linkService;
public TopologyViewIntentFilter(ServicesBundle services) {
this.intentService = services.intentService();
this.deviceService = services.deviceService();
this.hostService = services.hostService();
this.linkService = services.linkService();
}
// TODO: Review - do we need this signature, with sourceIntents??
// public List<Intent> findPathIntents(Set<Host> hosts, Set<Device> devices,
// Iterable<Intent> sourceIntents) {
// }
/**
* Finds all path (host-to-host or point-to-point) intents that pertains
* to the given hosts.
* Finds all path (host-to-host or point-to-point) intents that pertain
* to the given hosts and devices.
*
* @param hosts set of hosts to query by
* @param devices set of devices to query by
* @param sourceIntents collection of intents to search
* @return set of intents that 'match' all hosts and devices given
*/
List<Intent> findPathIntents(Set<Host> hosts, Set<Device> devices,
Iterable<Intent> sourceIntents) {
public List<Intent> findPathIntents(Set<Host> hosts, Set<Device> devices) {
// start with all intents
Iterable<Intent> sourceIntents = intentService.getIntents();
// Derive from this the set of edge connect points.
Set<ConnectPoint> edgePoints = getEdgePoints(hosts);
......
/*
* Copyright 2015 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.topo;
import org.onosproject.net.intent.Intent;
import org.onosproject.ui.topo.LinkHighlight;
/**
* Auxiliary data carrier for assigning a highlight class to a set of
* intents, for visualization in the topology view.
*/
public class TrafficClass {
private final LinkHighlight.Flavor flavor;
private final Iterable<Intent> intents;
private final boolean showTraffic;
public TrafficClass(LinkHighlight.Flavor flavor, Iterable<Intent> intents) {
this(flavor, intents, false);
}
public TrafficClass(LinkHighlight.Flavor flavor, Iterable<Intent> intents,
boolean showTraffic) {
this.flavor = flavor;
this.intents = intents;
this.showTraffic = showTraffic;
}
public LinkHighlight.Flavor flavor() {
return flavor;
}
public Iterable<Intent> intents() {
return intents;
}
public boolean showTraffic() {
return showTraffic;
}
}
......@@ -58,6 +58,7 @@
showHosts = false, // whether hosts are displayed
showOffline = true, // whether offline devices are displayed
nodeLock = false, // whether nodes can be dragged or not (locked)
fTimer, // timer for delayed force layout
fNodesTimer, // timer for delayed nodes update
fLinksTimer, // timer for delayed links update
dim, // the dimensions of the force layout [w,h]
......@@ -117,6 +118,7 @@
network.nodes.push(d);
lu[id] = d;
updateNodes();
fStart();
}
function updateDevice(data) {
......@@ -170,6 +172,7 @@
lu[d.egress] = lnk;
updateLinks();
}
fStart();
}
function updateHost(data) {
......@@ -215,6 +218,7 @@
aggregateLink(d, data);
lu[d.key] = d;
updateLinks();
fStart();
}
}
......@@ -322,6 +326,7 @@
// remove from lookup cache
delete lu[removed[0].key];
updateLinks();
fResume();
}
}
......@@ -343,6 +348,7 @@
// NOTE: upd is false if we were called from removeDeviceElement()
if (upd) {
updateNodes();
fResume();
}
}
......@@ -367,6 +373,7 @@
// remove from SVG
updateNodes();
fResume();
}
function updateHostVisibility() {
......@@ -520,8 +527,9 @@
fNodesTimer = $timeout(_updateNodes, 150);
}
// IMPLEMENTATION NOTE: _updateNodes() should NOT stop, start, or resume
// the force layout; that needs to be determined and implemented elsewhere
function _updateNodes() {
force.stop();
// select all the nodes in the layout:
node = nodeG.selectAll('.node')
.data(network.nodes, function (d) { return d.id; });
......@@ -536,7 +544,10 @@
.attr({
id: function (d) { return sus.safeId(d.id); },
class: mkSvgClass,
transform: function (d) { return sus.translate(d.x, d.y); },
transform: function (d) {
// Need to guard against NaN here ??
return sus.translate(d.x, d.y);
},
opacity: 0
})
.call(drag)
......@@ -564,7 +575,6 @@
// exiting node specifics:
exiting.filter('.host').each(td3.hostExit);
exiting.filter('.device').each(td3.deviceExit);
fStart();
}
// ==========================
......@@ -659,9 +669,10 @@
fLinksTimer = $timeout(_updateLinks, 150);
}
// IMPLEMENTATION NOTE: _updateLinks() should NOT stop, start, or resume
// the force layout; that needs to be determined and implemented elsewhere
function _updateLinks() {
var th = ts.theme();
force.stop();
link = linkG.selectAll('.link')
.data(network.links, function (d) { return d.key; });
......@@ -714,7 +725,6 @@
})
.style('opacity', 0.0)
.remove();
fStart();
}
......@@ -729,14 +739,23 @@
function fStart() {
if (!tos.isOblique()) {
$log.debug("Starting force-layout");
force.start();
if (fTimer) {
$timeout.cancel(fTimer);
}
fTimer = $timeout(function () {
$log.debug("Starting force-layout");
force.start();
}, 200);
}
}
var tickStuff = {
nodeAttr: {
transform: function (d) { return sus.translate(d.x, d.y); }
transform: function (d) {
var dx = isNaN(d.x) ? 0 : d.x,
dy = isNaN(d.y) ? 0 : d.y;
return sus.translate(dx, dy);
}
},
linkAttr: {
x1: function (d) { return d.position.x1; },
......@@ -1046,6 +1065,9 @@
force = drag = null;
// clean up $timeout promises
if (fTimer) {
$timeout.cancel(fTimer);
}
if (fNodesTimer) {
$timeout.cancel(fNodesTimer);
}
......
......@@ -293,7 +293,7 @@
findLinkById( id )
*/
var paths = data.paths;
var paths = data.links;
api.clearLinkTrafficStyle();
api.removeLinkLabels();
......
......@@ -114,7 +114,7 @@
}
if (!ev.shiftKey) {
deselectAll();
deselectAll(true);
}
selections[obj.id] = { obj: obj, el: el };
......@@ -135,7 +135,7 @@
}
}
function deselectAll() {
function deselectAll(skipUpdate) {
var something = (selectOrder.length > 0);
// deselect all nodes in the network...
......@@ -143,7 +143,9 @@
selections = {};
selectOrder = [];
api.updateDeviceColors();
updateDetail();
if (!skipUpdate) {
updateDetail();
}
// return true if something was selected
return something;
......
......@@ -42,9 +42,9 @@
// invoked in response to change in selection and/or mouseover/out:
function requestTrafficForMode() {
if (hoverMode === 'flows') {
if (trafficMode === 'flows') {
requestDeviceLinkFlows();
} else if (hoverMode === 'intents') {
} else if (trafficMode === 'intents') {
requestRelatedIntents();
} else {
cancelTraffic();
......@@ -175,7 +175,6 @@
}
// === -----------------------------------------------------
// === MODULE DEFINITION ===
......