ONOS-1479 -- GUI - augmenting topology view for extensibility: WIP.
- Major refactoring of TopologyViewMessageHandler and related classes.
Change-Id: I920f7f9f7317f3987a9a8da35ac086e9f8cab8d3
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java b/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java
new file mode 100644
index 0000000..23cd7d8
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/topo/AbstractHighlight.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+ }
+}
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java b/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java
new file mode 100644
index 0000000..1b721b8
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/topo/DeviceHighlight.java
@@ -0,0 +1,30 @@
+/*
+ * 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);
+ }
+
+
+}
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java b/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java
new file mode 100644
index 0000000..107fdd3
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java
@@ -0,0 +1,66 @@
+/*
+ * 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);
+ }
+}
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java b/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java
new file mode 100644
index 0000000..ff8b3be
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/topo/HostHighlight.java
@@ -0,0 +1,30 @@
+/*
+ * 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);
+ }
+
+
+}
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.java b/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.java
new file mode 100644
index 0000000..d8e4279
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/topo/LinkHighlight.java
@@ -0,0 +1,189 @@
+/*
+ * 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");
+}
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/TopoElementType.java b/core/api/src/main/java/org/onosproject/ui/topo/TopoElementType.java
new file mode 100644
index 0000000..dc32746
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/topo/TopoElementType.java
@@ -0,0 +1,25 @@
+/*
+ * 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
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java
index a311b2d..18a5acd 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java
@@ -24,7 +24,8 @@
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 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 ↔ B";
+ private static final String A_SINGLE_B = "A → 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 @@
// 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 ↔ B" : "A → B";
+ return link.two() != null ? A_BOTH_B : A_SINGLE_B;
}
}
+
}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
index b88ee4f..c2f54e4 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
@@ -48,7 +48,6 @@
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.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.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 @@
// 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 @@
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 @@
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 @@
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 @@
@Override
public void process(long sid, ObjectNode payload) {
stopSummaryMonitoring();
- stopTrafficMonitoring();
+ tmo.stop();
}
}
@@ -390,6 +377,9 @@
}
}
+
+ // ========= -----------------------------------------------------------------
+
// === TODO: move traffic related classes to traffic app
private final class AddHostIntent extends RequestHandler {
@@ -410,7 +400,7 @@
.build();
intentService.submit(intent);
- startMonitoringIntent(intent);
+ tmo.monitor(intent);
}
}
@@ -443,82 +433,11 @@
.build();
intentService.submit(intent);
- startMonitoringIntent(intent);
+ tmo.monitor(intent);
}
}
- private final class ReqRelatedIntents extends RequestHandler {
- private ReqRelatedIntents() {
- super(REQ_RELATED_INTENTS);
- }
-
- @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);
-// }
- }
- }
-
- private final class ReqNextIntent extends RequestHandler {
- private ReqNextIntent() {
- super(REQ_NEXT_INTENT);
- }
-
- @Override
- public void process(long sid, ObjectNode payload) {
- stopTrafficMonitoring();
- requestAnotherRelatedIntent(+1);
- }
- }
-
- private final class ReqPrevIntent extends RequestHandler {
- private ReqPrevIntent() {
- super(REQ_PREV_INTENT);
- }
-
- @Override
- public void process(long sid, ObjectNode payload) {
- stopTrafficMonitoring();
- requestAnotherRelatedIntent(-1);
- }
- }
-
- 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.SEL_INTENT, payload);
- requestSelectedIntentTraffic();
- startTrafficMonitoring();
- }
- }
+ // ========= -----------------------------------------------------------------
private final class ReqAllFlowTraffic extends RequestHandler {
private ReqAllFlowTraffic() {
@@ -527,8 +446,7 @@
@Override
public void process(long sid, ObjectNode payload) {
- trafficEvent = new TrafficEvent(TrafficEvent.Type.ALL_FLOW_TRAFFIC, payload);
- requestAllFlowTraffic();
+ tmo.monitor(Mode.ALL_FLOW_TRAFFIC);
}
}
@@ -539,8 +457,7 @@
@Override
public void process(long sid, ObjectNode payload) {
- trafficEvent = new TrafficEvent(TrafficEvent.Type.ALL_PORT_TRAFFIC, payload);
- requestAllPortTraffic();
+ tmo.monitor(Mode.ALL_PORT_TRAFFIC);
}
}
@@ -551,8 +468,55 @@
@Override
public void process(long sid, ObjectNode payload) {
- trafficEvent = new TrafficEvent(TrafficEvent.Type.DEV_LINK_FLOWS, payload);
- requestDeviceLinkFlows(payload);
+ NodeSelection nodeSelection =
+ new NodeSelection(payload, deviceService, hostService);
+ tmo.monitor(Mode.DEV_LINK_FLOWS, nodeSelection);
+ }
+ }
+
+ private final class ReqRelatedIntents extends RequestHandler {
+ private ReqRelatedIntents() {
+ super(REQ_RELATED_INTENTS);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ NodeSelection nodeSelection =
+ new NodeSelection(payload, deviceService, hostService);
+ tmo.monitor(Mode.RELATED_INTENTS, nodeSelection);
+ }
+ }
+
+ private final class ReqNextIntent extends RequestHandler {
+ private ReqNextIntent() {
+ super(REQ_NEXT_INTENT);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ tmo.selectNextIntent();
+ }
+ }
+
+ private final class ReqPrevIntent extends RequestHandler {
+ private ReqPrevIntent() {
+ super(REQ_PREV_INTENT);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ tmo.selectPreviousIntent();
+ }
+ }
+
+ private final class ReqSelectedIntentTraffic extends RequestHandler {
+ private ReqSelectedIntentTraffic() {
+ super(REQ_SEL_INTENT_TRAFFIC);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ tmo.monitor(Mode.SEL_INTENT);
}
}
@@ -563,14 +527,16 @@
@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 @@
private void cancelAllRequests() {
stopSummaryMonitoring();
- stopTrafficMonitoring();
+ tmo.stop();
}
// Sends all controller nodes to the client as node-added messages.
@@ -641,18 +607,6 @@
}
}
-
- 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 @@
}
- 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 @@
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 @@
}
}
- // 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 @@
@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@");
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
index 9265e5f..130f88f 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
@@ -18,7 +18,6 @@
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.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.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_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_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 @@
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 @@
protected TopologyService topologyService;
protected TunnelService tunnelService;
- protected enum StatsType {
- FLOW, PORT
- }
+ protected ServicesBundle servicesBundle;
private String version;
@@ -187,6 +158,11 @@
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 @@
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 @@
JsonUtils.node(payload, "memento"));
}
+
// -----------------------------------------------------------------------
// Create models of the data to return, that overlays can adjust / augment
@@ -527,24 +446,24 @@
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 @@
}
// 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 @@
return count;
}
-
// Returns host details response.
protected PropertyPanel hostDetails(HostId hostId, long sid) {
Host host = hostService.getHost(hostId);
@@ -589,270 +507,98 @@
.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) {
+ // ----------------------------------------------------------------------
+
+ /**
+ * 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();
- ArrayNode paths = arrayNode();
- payload.set("paths", paths);
- ObjectNode pathNodeN = objectNode();
- ArrayNode linksNodeN = arrayNode();
- ArrayNode labelsN = arrayNode();
+ ArrayNode devices = arrayNode();
+ ArrayNode hosts = arrayNode();
+ ArrayNode links = arrayNode();
- pathNodeN.put("class", "plain").put("traffic", false);
- pathNodeN.set("links", linksNodeN);
- pathNodeN.set("labels", labelsN);
- paths.add(pathNodeN);
+ payload.set("devices", devices);
+ payload.set("hosts", hosts);
+ payload.set("links", links);
- ObjectNode pathNodeT = objectNode();
- ArrayNode linksNodeT = arrayNode();
- ArrayNode labelsT = arrayNode();
+ highlights.devices().forEach(dh -> devices.add(json(dh)));
+ highlights.hosts().forEach(hh -> hosts.add(json(hh)));
+ jsonifyLinks(links, highlights.links());
- 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);
+ return payload;
}
- private Load getLinkLoad(Link link) {
- if (link.src().elementId() instanceof DeviceId) {
- return flowStatsService.load(link);
- }
- return null;
- }
+ 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
- private void addEdgeLinks(Map<LinkKey, BiLink> biLinks) {
- hostService.getHosts().forEach(host -> {
- addLink(biLinks, createEdgeLink(host, true));
- addLink(biLinks, createEdgeLink(host, false));
- });
- }
-
- private Map<LinkKey, BiLink> consolidateLinks(Iterable<Link> links) {
- Map<LinkKey, BiLink> biLinks = new HashMap<>();
- for (Link link : links) {
- addLink(biLinks, link);
- }
- return biLinks;
- }
-
- // Produces JSON message to trigger flow overview visualization
- protected ObjectNode flowSummaryMessage(Set<Device> devices) {
- ObjectNode payload = objectNode();
- ArrayNode paths = arrayNode();
- payload.set("paths", paths);
-
- 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);
- }
-
- 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);
- }
+ // 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);
+ Map<String, List<String>> linkIdMap = new HashMap<>();
+ Map<String, List<String>> linkLabelMap = new HashMap<>();
+ List<String> ids;
+ List<String> labels;
- // Classify links based on their traffic traffic first...
- Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
+ for (LinkHighlight lh : hilites) {
+ String cls = lh.cssClasses();
+ ids = linkIdMap.get(cls);
+ labels = linkLabelMap.get(cls);
- // 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);
- }
-
- // 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;
- }
-
- // 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);
- }
- }
- 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);
- }
- }
- }
-
-
- 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;
- }
-
- // 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;
- }
- 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;
+ if (ids == null) { // labels will be null also
+ ids = new ArrayList<>();
+ linkIdMap.put(cls, ids);
+ labels = new ArrayList<>();
+ linkLabelMap.put(cls, labels);
}
- } 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;
+ ids.add(lh.elementId());
+ labels.add(lh.label());
}
- DecimalFormat format = new DecimalFormat("#,###.##");
- return format.format(value) + " " + unit;
+
+ for (String cls : linkIdMap.keySet()) {
+ ObjectNode group = objectNode();
+ links.add(group);
+
+ group.put("class", cls);
+
+ 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);
+ }
}
- // 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(DeviceHighlight dh) {
+ // TODO: implement this once we know what a device highlight looks like
+ return objectNode();
+ }
+
+ 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 @@
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;
- }
- }
-
}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorObject.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorObject.java
new file mode 100644
index 0000000..0826655
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorObject.java
@@ -0,0 +1,594 @@
+/*
+ * 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);
+ }
+ }
+ }
+
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/BiLink.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/BiLink.java
new file mode 100644
index 0000000..5ab4e0e
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/BiLink.java
@@ -0,0 +1,244 @@
+/*
+ * 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;
+ }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/IntentSelection.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/IntentSelection.java
new file mode 100644
index 0000000..eb959c5
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/IntentSelection.java
@@ -0,0 +1,171 @@
+/*
+ * 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 +
+ '}';
+ }
+
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/LinkStatsType.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/LinkStatsType.java
new file mode 100644
index 0000000..589cddd
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/LinkStatsType.java
@@ -0,0 +1,43 @@
+/*
+ * 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
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/NodeSelection.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/NodeSelection.java
new file mode 100644
index 0000000..fa776b3
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/NodeSelection.java
@@ -0,0 +1,187 @@
+/*
+ * 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;
+ }
+
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/ServicesBundle.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/ServicesBundle.java
new file mode 100644
index 0000000..4282cdc
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/ServicesBundle.java
@@ -0,0 +1,96 @@
+/*
+ * 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;
+ }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUtils.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUtils.java
new file mode 100644
index 0000000..8d6b319
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUtils.java
@@ -0,0 +1,184 @@
+/*
+ * 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;
+ }
+
+
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewIntentFilter.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopologyViewIntentFilter.java
similarity index 92%
rename from web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewIntentFilter.java
rename to web/gui/src/main/java/org/onosproject/ui/impl/topo/TopologyViewIntentFilter.java
index c3f58f7..1bd2b58 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewIntentFilter.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopologyViewIntentFilter.java
@@ -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 @@
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);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficClass.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficClass.java
new file mode 100644
index 0000000..1389aba
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TrafficClass.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+ }
+}
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index 0595393..f3c053e 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -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);
}
diff --git a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
index 41c8e1e..a049948 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
@@ -293,7 +293,7 @@
findLinkById( id )
*/
- var paths = data.paths;
+ var paths = data.links;
api.clearLinkTrafficStyle();
api.removeLinkLabels();
diff --git a/web/gui/src/main/webapp/app/view/topo/topoSelect.js b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
index 2e73ea2..72a689f 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoSelect.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
@@ -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;
diff --git a/web/gui/src/main/webapp/app/view/topo/topoTraffic.js b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
index 27ec979..9308542 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
@@ -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 ===