diff --git a/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java b/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java index 9e03a51630..f1ea508c9f 100644 --- a/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java +++ b/core/api/src/main/java/org/onosproject/ui/UiExtensionService.java @@ -55,6 +55,22 @@ public interface UiExtensionService { default void unregister(UiGlyphFactory factory) { } + /** + * Registers the specified topo hilighter factory. + * + * @param factory UI topo higlighter factory to register + */ + default void register(UiTopoHighlighterFactory factory) { + } + + /** + * Unregisters the specified user interface extension. + * + * @param factory UI topo higlighter factory to unregister + */ + default void unregister(UiTopoHighlighterFactory factory) { + } + /** * Returns the list of registered user interface extensions. * @@ -79,6 +95,15 @@ public interface UiExtensionService { return new ArrayList(); } + /** + * Returns the list of registered topo highlighter factories. + * + * @return list of highlighter factories + */ + default List getTopoHighlighterFactories() { + return new ArrayList(); + } + /** * Returns the navigation pane localization bundle. * diff --git a/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighter.java b/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighter.java new file mode 100644 index 0000000000..9c12603e55 --- /dev/null +++ b/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighter.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-present Open Networking Foundation + * + * 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; + +import org.onosproject.ui.topo.Highlights; + +/** + * Abstraction of an entity capable of generating a set of topology highlights + * for devices, hosts and links. + */ +public interface UiTopoHighlighter { + + /** + * Returns the self-assigned name of the hilighter. + * + * @return highlighter name + */ + String name(); + + /** + * Generate a set of highlights. + * + * @return topology highlights + */ + Highlights createHighlights(); + +} diff --git a/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighterFactory.java b/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighterFactory.java new file mode 100644 index 0000000000..8a2d9767b3 --- /dev/null +++ b/core/api/src/main/java/org/onosproject/ui/UiTopoHighlighterFactory.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-present Open Networking Foundation + * + * 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; + +/** + * Abstraction of an entity capable of producing a a new topology highlighter. + */ +public interface UiTopoHighlighterFactory { + + /** + * Produces a new topology highlighter. + * + * @return newly created topology highlighter + */ + UiTopoHighlighter newTopoHighlighter(); + +} diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/SampleHighlighterFactory.java b/web/gui/src/main/java/org/onosproject/ui/impl/SampleHighlighterFactory.java new file mode 100644 index 0000000000..509d817788 --- /dev/null +++ b/web/gui/src/main/java/org/onosproject/ui/impl/SampleHighlighterFactory.java @@ -0,0 +1,117 @@ +/* + * Copyright 2021-present Open Networking Foundation + * + * 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 org.onosproject.net.EdgeLink; +import org.onosproject.net.Host; +import org.onosproject.net.Link; +import org.onosproject.net.host.HostService; +import org.onosproject.net.link.LinkService; +import org.onosproject.ui.UiExtensionService; +import org.onosproject.ui.UiTopoHighlighter; +import org.onosproject.ui.UiTopoHighlighterFactory; +import org.onosproject.ui.topo.BaseLink; +import org.onosproject.ui.topo.BaseLinkMap; +import org.onosproject.ui.topo.Highlights; +import org.onosproject.ui.topo.HostHighlight; +import org.onosproject.ui.topo.LinkHighlight; +import org.onosproject.ui.topo.Mod; +import org.onosproject.ui.topo.NodeBadge; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; + +import static org.onosproject.net.DefaultEdgeLink.createEdgeLinks; +import static org.onosproject.ui.topo.NodeBadge.text; + +@Component(immediate = false) +public class SampleHighlighterFactory { + + @Reference(cardinality = ReferenceCardinality.MANDATORY) + protected UiExtensionService uiExtensionService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY) + protected LinkService linkService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY) + protected HostService hostService; + + private UiTopoHighlighterFactory foo = () -> + new TestHighlighter("foo", new Mod("style=\"stroke: #0ff; stroke-width: 10px;\"")); + private UiTopoHighlighterFactory bar = () -> + new TestHighlighter("bar", new Mod("style=\"stroke: #f0f; stroke-width: 4px; stroke-dasharray: 5 2;\"")); + + @Activate + protected void activate() { + uiExtensionService.register(foo); + uiExtensionService.register(bar); + } + + @Deactivate + protected void deactivate() { + uiExtensionService.unregister(foo); + uiExtensionService.unregister(bar); + } + + private final class TestHighlighter implements UiTopoHighlighter { + + private final String name; + private final Mod mod; + + private TestHighlighter(String name, Mod mod) { + this.name = name; + this.mod = mod; + } + + @Override + public String name() { + return name; + } + + @Override + public Highlights createHighlights() { + Highlights highlights = new Highlights(); + BaseLinkMap linkMap = new BaseLinkMap(); + + // Create a map of base bi-links from the set of active links first. + for (Link link : linkService.getActiveLinks()) { + linkMap.add(link); + } + + for (Host host : hostService.getHosts()) { + for (EdgeLink link : createEdgeLinks(host, false)) { + linkMap.add(link); + } + + // Also add a host badge for kicks. + HostHighlight hostHighlight = new HostHighlight(host.id().toString()); + hostHighlight.setBadge(text(NodeBadge.Status.WARN, name)); + highlights.add(hostHighlight); + } + + // Now scan through the links and annotate them with desired highlights + for (BaseLink link : linkMap.biLinks()) { + highlights.add(new LinkHighlight(link.linkId(), LinkHighlight.Flavor.PRIMARY_HIGHLIGHT) + .addMod(mod).setLabel(name + "-" + link.one().src().port())); + } + + return highlights; + } + } + +} 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 455b0f334d..cae4760d81 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 @@ -113,6 +113,7 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase { private static final String REQ_SEL_INTENT_TRAFFIC = "requestSelectedIntentTraffic"; private static final String SEL_INTENT = "selectIntent"; private static final String REQ_ALL_TRAFFIC = "requestAllTraffic"; + private static final String REQ_CUSTOM_TRAFFIC = "requestCustomTraffic"; private static final String REQ_DEV_LINK_FLOWS = "requestDeviceLinkFlows"; private static final String CANCEL_TRAFFIC = "cancelTraffic"; private static final String REQ_SUMMARY = "requestSummary"; @@ -246,6 +247,7 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase { new RemoveIntents(), new ReqAllTraffic(), + new ReqCustomTraffic(), new ReqDevLinkFlows(), new ReqRelatedIntents(), new ReqNextIntent(), @@ -623,6 +625,17 @@ public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase { } } + private final class ReqCustomTraffic extends RequestHandler { + private ReqCustomTraffic() { + super(REQ_CUSTOM_TRAFFIC); + } + + @Override + public void process(ObjectNode payload) { + traffic.monitor((int) number(payload, "index")); + } + } + private NodeSelection makeNodeSelection(ObjectNode payload) { return new NodeSelection(payload, services.device(), services.host(), services.link()); diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java index 9d9b3c31fd..b585ffc24f 100644 --- a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java +++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java @@ -150,6 +150,14 @@ public class TrafficMonitor extends TrafficMonitorBase { msgHandler.sendHighlights(intentTraffic()); } + @Override + protected void sendCustomTraffic() { + log.debug("sendCustomTraffic"); + if (topoHighlighter != null) { + msgHandler.sendHighlights(topoHighlighter.createHighlights()); + } + } + @Override protected void sendClearHighlights() { log.debug("sendClearHighlights"); diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java index 2f14ac407e..ae3c9098a0 100644 --- a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java +++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitorBase.java @@ -38,6 +38,9 @@ import org.onosproject.net.DefaultEdgeLink; import org.onosproject.net.DeviceId; import org.onosproject.net.Link; import org.onosproject.net.statistic.Load; +import org.onosproject.ui.UiExtensionService; +import org.onosproject.ui.UiTopoHighlighter; +import org.onosproject.ui.UiTopoHighlighterFactory; import org.onosproject.ui.impl.topo.TopoologyTrafficMessageHandlerAbstract; import org.onosproject.ui.impl.topo.util.IntentSelection; import org.onosproject.ui.impl.topo.util.ServicesBundle; @@ -69,8 +72,7 @@ import static org.onosproject.net.DefaultEdgeLink.createEdgeLink; import static org.onosproject.net.statistic.PortStatisticsService.MetricType.BYTES; import static org.onosproject.net.statistic.PortStatisticsService.MetricType.PACKETS; import static org.onosproject.net.DefaultEdgeLink.createEdgeLinks; -import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.IDLE; -import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.SELECTED_INTENT; +import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.*; /** * Base superclass for traffic monitor (both 'classic' and 'topo2' versions). @@ -85,6 +87,7 @@ public abstract class TrafficMonitorBase extends AbstractTopoMonitor { protected IntentSelection selectedIntents = null; protected final TopoologyTrafficMessageHandlerAbstract msgHandler; protected NodeSelection selectedNodes = null; + protected UiTopoHighlighter topoHighlighter = null; protected void sendSelectedIntents() { log.debug("sendSelectedIntents: {}", selectedIntents); @@ -285,7 +288,8 @@ public abstract class TrafficMonitorBase extends AbstractTopoMonitor { ALL_PORT_TRAFFIC_PKT_PS, DEV_LINK_FLOWS, RELATED_INTENTS, - SELECTED_INTENT + SELECTED_INTENT, + CUSTOM_TRAFFIC_MONITOR } /** @@ -375,6 +379,22 @@ public abstract class TrafficMonitorBase extends AbstractTopoMonitor { } } + + public synchronized void monitor(int index) { + mode = CUSTOM_TRAFFIC_MONITOR; + List factories = services.get(UiExtensionService.class) + .getTopoHighlighterFactories(); + if (factories.isEmpty()) { + return; + } + + UiTopoHighlighterFactory factory = factories.get(index % factories.size()); + topoHighlighter = factory.newTopoHighlighter(); + clearSelection(); + scheduleTask(); + sendCustomTraffic(); + } + /** * Monitor for traffic data to be sent back to the web client, under * the given mode, using the given selection of devices and hosts. @@ -473,6 +493,12 @@ public abstract class TrafficMonitorBase extends AbstractTopoMonitor { */ protected abstract void sendSelectedIntentTraffic(); + /** + * Subclass should compile and send appropriate highlights data showing + * custom traffic on links. + */ + protected abstract void sendCustomTraffic(); + /** * Subclass should send a "clear highlights" event. */ @@ -712,6 +738,9 @@ public abstract class TrafficMonitorBase extends AbstractTopoMonitor { case SELECTED_INTENT: sendSelectedIntentTraffic(); break; + case CUSTOM_TRAFFIC_MONITOR: + sendCustomTraffic(); + break; default: // RELATED_INTENTS and IDLE modes should never invoke diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java index 7d5c32484d..73de4c1af9 100644 --- a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java +++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java @@ -49,6 +49,7 @@ import org.onosproject.ui.UiPreferencesService; import org.onosproject.ui.UiSessionToken; import org.onosproject.ui.UiTokenService; import org.onosproject.ui.UiTopo2OverlayFactory; +import org.onosproject.ui.UiTopoHighlighterFactory; import org.onosproject.ui.UiTopoMap; import org.onosproject.ui.UiTopoMapFactory; import org.onosproject.ui.UiTopoOverlayFactory; @@ -140,6 +141,7 @@ public class UiExtensionManager private final List extensions = Lists.newArrayList(); private final List glyphs = Lists.newArrayList(); + private final List highlighterFactories = Lists.newArrayList(); // Map of views to extensions private final Map views = Maps.newHashMap(); @@ -365,6 +367,22 @@ public class UiExtensionManager } } + @Override + public synchronized void register(UiTopoHighlighterFactory factory) { + checkPermission(UI_WRITE); + if (!highlighterFactories.contains(factory)) { + highlighterFactories.add(factory); + UiWebSocketServlet.sendToAll(GUI_ADDED, null); + } + } + + @Override + public synchronized void unregister(UiTopoHighlighterFactory factory) { + checkPermission(UI_WRITE); + highlighterFactories.remove(factory); + UiWebSocketServlet.sendToAll(GUI_REMOVED, null); + } + @Override public synchronized List getExtensions() { checkPermission(UI_READ); @@ -377,6 +395,12 @@ public class UiExtensionManager return ImmutableList.copyOf(glyphs); } + @Override + public synchronized List getTopoHighlighterFactories() { + checkPermission(UI_READ); + return ImmutableList.copyOf(highlighterFactories); + } + @Override public synchronized UiExtension getViewExtension(String viewId) { checkPermission(UI_READ); diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java index 76b9dc5381..6a41cf2358 100644 --- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java +++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Traffic2Monitor.java @@ -64,6 +64,10 @@ public class Traffic2Monitor extends TrafficMonitorBase { msgHandler.sendHighlights(trafficSummary(TrafficLink.StatsType.FLOW_STATS)); } + @Override + protected void sendCustomTraffic() { + } + @Override protected void sendAllPortTrafficBits() { log.debug("TOPO-2-TRAFFIC: sendAllPortTrafficBits"); diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java index e58b0f223c..15677184cd 100644 --- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java +++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/util/ServicesBundle.java @@ -37,6 +37,8 @@ import static com.google.common.base.Preconditions.checkNotNull; */ public class ServicesBundle { + private ServiceDirectory directory; + private ClusterService clusterService; private TopologyService topologyService; @@ -60,6 +62,7 @@ public class ServicesBundle { */ public ServicesBundle(ServiceDirectory directory) { checkNotNull(directory, "Directory cannot be null"); + this.directory = directory; clusterService = directory.get(ClusterService.class); @@ -184,4 +187,15 @@ public class ServicesBundle { public PortStatisticsService portStats() { return portStatsService; } + + /** + * Returns the implementation of the specified service class. + * + * @param serviceClass service class + * @param class of service + * @return implementation of the service class + */ + public T get(Class serviceClass) { + return directory.get(serviceClass); + } } diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties index c2cdfaecdd..8f9334eb0c 100644 --- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties +++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties @@ -103,6 +103,7 @@ tr_btn_show_device_flows=Show Device Flows tr_btn_show_related_traffic=Show Related Traffic tr_btn_cancel_monitoring=Cancel traffic monitoring tr_btn_monitor_all=Monitor all traffic +tr_btn_monitor_custom_all=Custom traffic monitor tr_btn_show_dev_link_flows=Show device link flows tr_btn_show_all_rel_intents=Show all related intents tr_btn_show_prev_rel_intent=Show previous related intent 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 8819107ecf..912e851ccb 100644 --- a/web/gui/src/main/webapp/app/view/topo/topoForce.js +++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js @@ -1023,6 +1023,7 @@ function clearLinkTrafficStyle() { link.style('stroke-width', null) + .style('stroke', null) .classed(allTrafficClasses, false); } 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 4e9133a194..7cf47ffe38 100644 --- a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js +++ b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js @@ -398,6 +398,8 @@ } }); + const stylePattern = /style=\"[^\"]*\"/g; + data.links.forEach(function (link) { var ldata = api.findLinkById(link.id); @@ -405,6 +407,14 @@ if (!link.subdue) { api.unsupLink(ldata.key, less); } + var styleFound = link.css.match(stylePattern); + if (styleFound) { + link.css = link.css.replace(stylePattern, ''); + var style = styleFound[0].replace('style="', '').replace('"$', '') + ldata.el.attr('style', style); + } else { + ldata.el.attr('style', ''); + } ldata.el.classed(link.css, true); ldata.label = link.label; 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 640a233e80..c643494132 100644 --- a/web/gui/src/main/webapp/app/view/topo/topoTraffic.js +++ b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js @@ -47,7 +47,8 @@ // internal state var trafficMode = null, hoverMode = null, - allTrafficIndex = 0; + allTrafficIndex = 0, + customTrafficIndex = 0; // === ----------------------------------------------------- @@ -137,6 +138,16 @@ allTrafficIndex = (allTrafficIndex + 1) % 3; } + function showCustomTraffic() { + trafficMode = 'allCustom'; + hoverMode = null; + wss.sendEvent('requestCustomTraffic', { + index: customTrafficIndex, + }); + flash.flash('Custom Traffic'); + customTrafficIndex = customTrafficIndex + 1; + } + function showDeviceLinkFlows() { trafficMode = hoverMode = 'flows'; requestDeviceLinkFlows(); @@ -270,6 +281,7 @@ showNextIntent: showNextIntent, showSelectedIntentTraffic: showSelectedIntentTraffic, selectIntent: selectIntent, + showCustomTraffic: showCustomTraffic, // invoked from mouseover/mouseout and selection change requestTrafficForMode: requestTrafficForMode, diff --git a/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js b/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js index 143aa30089..2057f7b708 100644 --- a/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js +++ b/web/gui/src/main/webapp/app/view/topo/topoTrafficNew.js @@ -109,9 +109,14 @@ tt: function () { return topoLion('tr_btn_monitor_sel_intent'); }, gid: 'm_intentTraffic', }, + C: { + cb: function () { tts.showCustomTraffic(); }, + tt: function () { return topoLion('tr_btn_monitor_custom_all'); }, + gid: 'm_allTraffic', + }, _keyOrder: [ - '0', 'A', 'F', 'V', 'leftArrow', 'rightArrow', 'W', + '0', 'A', 'F', 'V', 'leftArrow', 'rightArrow', 'W', 'C' ], },