Added mechanism for apps to easily add their own custom link/node/host highlighting wihout having to create a new UI extensions

Change-Id: Iefa21d76190c60db79a4b07a8b22e301d29fe58e
This commit is contained in:
Thomas Vachuska 2021-03-30 16:31:34 -07:00 committed by Charles Chan
parent d37181d7d1
commit 2b4de873e4
15 changed files with 339 additions and 5 deletions

View File

@ -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<UiGlyph>();
}
/**
* Returns the list of registered topo highlighter factories.
*
* @return list of highlighter factories
*/
default List<UiTopoHighlighterFactory> getTopoHighlighterFactories() {
return new ArrayList<UiTopoHighlighterFactory>();
}
/**
* Returns the navigation pane localization bundle.
*

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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;
}
}
}

View File

@ -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());

View File

@ -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");

View File

@ -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<UiTopoHighlighterFactory> 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

View File

@ -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<UiExtension> extensions = Lists.newArrayList();
private final List<UiGlyph> glyphs = Lists.newArrayList();
private final List<UiTopoHighlighterFactory> highlighterFactories = Lists.newArrayList();
// Map of views to extensions
private final Map<String, UiExtension> 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<UiExtension> getExtensions() {
checkPermission(UI_READ);
@ -377,6 +395,12 @@ public class UiExtensionManager
return ImmutableList.copyOf(glyphs);
}
@Override
public synchronized List<UiTopoHighlighterFactory> getTopoHighlighterFactories() {
checkPermission(UI_READ);
return ImmutableList.copyOf(highlighterFactories);
}
@Override
public synchronized UiExtension getViewExtension(String viewId) {
checkPermission(UI_READ);

View File

@ -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");

View File

@ -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 <T> class of service
* @return implementation of the service class
*/
public <T> T get(Class<T> serviceClass) {
return directory.get(serviceClass);
}
}

View File

@ -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

View File

@ -1023,6 +1023,7 @@
function clearLinkTrafficStyle() {
link.style('stroke-width', null)
.style('stroke', null)
.classed(allTrafficClasses, false);
}

View File

@ -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;

View File

@ -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,

View File

@ -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'
],
},