Thomas Vachuska

Deprecating old web-socket stuff and adding ability for client-side message hand…

…ler registration. Failover still to be done and same for the async hooks.

Change-Id: I6029c91eb1a04e01401e495b9673ddaea728e215
......@@ -31,6 +31,7 @@ import java.util.TimerTask;
/**
* Web socket servlet capable of creating various sockets for the user interface.
*/
@Deprecated
public class GuiWebSocketServlet extends WebSocketServlet {
private static final long PING_DELAY_MS = 5000;
......
/*
* 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.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableSet;
import org.onlab.osgi.ServiceDirectory;
import org.onlab.util.AbstractAccumulator;
import org.onlab.util.Accumulator;
import org.onosproject.cluster.ClusterEvent;
import org.onosproject.cluster.ClusterEventListener;
import org.onosproject.cluster.ControllerNode;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.event.Event;
import org.onosproject.mastership.MastershipAdminService;
import org.onosproject.mastership.MastershipEvent;
import org.onosproject.mastership.MastershipListener;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Device;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.HostLocation;
import org.onosproject.net.Link;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceListener;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRuleEvent;
import org.onosproject.net.flow.FlowRuleListener;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostListener;
import org.onosproject.net.intent.HostToHostIntent;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentEvent;
import org.onosproject.net.intent.IntentListener;
import org.onosproject.net.intent.MultiPointToSinglePointIntent;
import org.onosproject.net.link.LinkEvent;
import org.onosproject.net.link.LinkListener;
import org.onosproject.ui.UiConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
import static org.onosproject.net.DeviceId.deviceId;
import static org.onosproject.net.HostId.hostId;
import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_UPDATED;
import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
/**
* Web socket capable of interacting with the GUI topology view.
*/
public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
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 Comparator<? super ControllerNode> NODE_COMPARATOR =
new Comparator<ControllerNode>() {
@Override
public int compare(ControllerNode o1, ControllerNode o2) {
return o1.id().toString().compareTo(o2.id().toString());
}
};
private final Timer timer = new Timer("topology-view");
private static final int MAX_EVENTS = 1000;
private static final int MAX_BATCH_MS = 5000;
private static final int MAX_IDLE_MS = 1000;
private ApplicationId appId;
private final ClusterEventListener clusterListener = new InternalClusterListener();
private final MastershipListener mastershipListener = new InternalMastershipListener();
private final DeviceListener deviceListener = new InternalDeviceListener();
private final LinkListener linkListener = new InternalLinkListener();
private final HostListener hostListener = new InternalHostListener();
private final IntentListener intentListener = new InternalIntentListener();
private final FlowRuleListener flowListener = new InternalFlowListener();
private final Accumulator<Event> eventAccummulator = new InternalEventAccummulator();
private TimerTask trafficTask;
private ObjectNode trafficEvent;
private TimerTask summaryTask;
private ObjectNode summaryEvent;
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;
/**
* Creates a new web-socket for serving data to GUI topology view.
*/
public TopologyViewMessageHandler() {
super(ImmutableSet.of("topoStart", "topoStop",
"requestDetails",
"updateMeta",
"addHostIntent",
"addMultiSourceIntent",
"requestRelatedIntents",
"requestNextRelatedIntent",
"requestPrevRelatedIntent",
"requestSelectedIntentTraffic",
"requestAllTraffic",
"requestDeviceLinkFlows",
"cancelTraffic",
"requestSummary",
"cancelSummary",
"equalizeMasters"
));
}
@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);
}
@Override
public void destroy() {
cancelAllRequests();
super.destroy();
}
// Processes the specified event.
@Override
public void process(ObjectNode event) {
String type = string(event, "event", "unknown");
if (type.equals("requestDetails")) {
requestDetails(event);
} else if (type.equals("updateMeta")) {
updateMetaUi(event);
} else if (type.equals("addHostIntent")) {
createHostIntent(event);
} else if (type.equals("addMultiSourceIntent")) {
createMultiSourceIntent(event);
} else if (type.equals("requestRelatedIntents")) {
stopTrafficMonitoring();
requestRelatedIntents(event);
} else if (type.equals("requestNextRelatedIntent")) {
stopTrafficMonitoring();
requestAnotherRelatedIntent(event, +1);
} else if (type.equals("requestPrevRelatedIntent")) {
stopTrafficMonitoring();
requestAnotherRelatedIntent(event, -1);
} else if (type.equals("requestSelectedIntentTraffic")) {
requestSelectedIntentTraffic(event);
startTrafficMonitoring(event);
} else if (type.equals("requestAllTraffic")) {
requestAllTraffic(event);
startTrafficMonitoring(event);
} else if (type.equals("requestDeviceLinkFlows")) {
requestDeviceLinkFlows(event);
startTrafficMonitoring(event);
} else if (type.equals("cancelTraffic")) {
cancelTraffic(event);
} else if (type.equals("requestSummary")) {
requestSummary(event);
startSummaryMonitoring(event);
} else if (type.equals("cancelSummary")) {
stopSummaryMonitoring();
} else if (type.equals("equalizeMasters")) {
equalizeMasters(event);
} else if (type.equals("topoStart")) {
sendAllInitialData();
} else if (type.equals("topoStop")) {
cancelAllRequests();
}
}
// Sends the specified data to the client.
protected synchronized void sendMessage(ObjectNode data) {
UiConnection connection = connection();
if (connection != null) {
connection.sendMessage(data);
}
}
private void sendAllInitialData() {
addListeners();
sendAllInstances(null);
sendAllDevices();
sendAllLinks();
sendAllHosts();
}
private void cancelAllRequests() {
stopSummaryMonitoring();
stopTrafficMonitoring();
removeListeners();
}
// Sends all controller nodes to the client as node-added messages.
private void sendAllInstances(String messageType) {
List<ControllerNode> nodes = new ArrayList<>(clusterService.getNodes());
Collections.sort(nodes, NODE_COMPARATOR);
for (ControllerNode node : nodes) {
sendMessage(instanceMessage(new ClusterEvent(INSTANCE_ADDED, node),
messageType));
}
}
// Sends all devices to the client as device-added messages.
private void sendAllDevices() {
// Send optical first, others later for layered rendering
for (Device device : deviceService.getDevices()) {
if (device.type() == Device.Type.ROADM) {
sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
}
}
for (Device device : deviceService.getDevices()) {
if (device.type() != Device.Type.ROADM) {
sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
}
}
}
// Sends all links to the client as link-added messages.
private void sendAllLinks() {
// Send optical first, others later for layered rendering
for (Link link : linkService.getLinks()) {
if (link.type() == Link.Type.OPTICAL) {
sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
}
}
for (Link link : linkService.getLinks()) {
if (link.type() != Link.Type.OPTICAL) {
sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
}
}
}
// Sends all hosts to the client as host-added messages.
private void sendAllHosts() {
for (Host host : hostService.getHosts()) {
sendMessage(hostMessage(new HostEvent(HOST_ADDED, host)));
}
}
// Sends back device or host details.
private void requestDetails(ObjectNode event) {
ObjectNode payload = payload(event);
String type = string(payload, "class", "unknown");
long sid = number(event, "sid");
if (type.equals("device")) {
sendMessage(deviceDetails(deviceId(string(payload, "id")), sid));
} else if (type.equals("host")) {
sendMessage(hostDetails(hostId(string(payload, "id")), sid));
}
}
// Creates host-to-host intent.
private void createHostIntent(ObjectNode event) {
ObjectNode payload = payload(event);
long id = number(event, "sid");
// TODO: add protection against device ids and non-existent hosts.
HostId one = hostId(string(payload, "one"));
HostId two = hostId(string(payload, "two"));
HostToHostIntent intent =
new HostToHostIntent(appId, one, two,
DefaultTrafficSelector.builder().build(),
DefaultTrafficTreatment.builder().build());
intentService.submit(intent);
startMonitoringIntent(event, intent);
}
// Creates multi-source-to-single-dest intent.
private void createMultiSourceIntent(ObjectNode event) {
ObjectNode payload = payload(event);
long id = number(event, "sid");
// TODO: add protection against device ids and non-existent hosts.
Set<HostId> src = getHostIds((ArrayNode) payload.path("src"));
HostId dst = hostId(string(payload, "dst"));
Host dstHost = hostService.getHost(dst);
Set<ConnectPoint> ingressPoints = getHostLocations(src);
// FIXME: clearly, this is not enough
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthDst(dstHost.mac()).build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
MultiPointToSinglePointIntent intent =
new MultiPointToSinglePointIntent(appId, selector, treatment,
ingressPoints, dstHost.location());
intentService.submit(intent);
startMonitoringIntent(event, intent);
}
private synchronized void startMonitoringIntent(ObjectNode event, Intent intent) {
selectedHosts = new HashSet<>();
selectedDevices = new HashSet<>();
selectedIntents = new ArrayList<>();
selectedIntents.add(intent);
currentIntentIndex = -1;
requestAnotherRelatedIntent(event, +1);
requestSelectedIntentTraffic(event);
}
private Set<ConnectPoint> getHostLocations(Set<HostId> hostIds) {
Set<ConnectPoint> points = new HashSet<>();
for (HostId hostId : hostIds) {
points.add(getHostLocation(hostId));
}
return points;
}
private HostLocation getHostLocation(HostId hostId) {
return hostService.getHost(hostId).location();
}
// Produces a list of host ids from the specified JSON array.
private Set<HostId> getHostIds(ArrayNode ids) {
Set<HostId> hostIds = new HashSet<>();
for (JsonNode id : ids) {
hostIds.add(hostId(id.asText()));
}
return hostIds;
}
private synchronized long startTrafficMonitoring(ObjectNode event) {
stopTrafficMonitoring();
trafficEvent = event;
trafficTask = new TrafficMonitor();
timer.schedule(trafficTask, TRAFFIC_FREQUENCY, TRAFFIC_FREQUENCY);
return number(event, "sid");
}
private synchronized void stopTrafficMonitoring() {
if (trafficTask != null) {
trafficTask.cancel();
trafficTask = null;
trafficEvent = null;
}
}
// Subscribes for host traffic messages.
private synchronized void requestAllTraffic(ObjectNode event) {
long sid = startTrafficMonitoring(event);
sendMessage(trafficSummaryMessage(sid));
}
private void requestDeviceLinkFlows(ObjectNode event) {
ObjectNode payload = payload(event);
long sid = startTrafficMonitoring(event);
// 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 = string(payload, "hover");
if (!isNullOrEmpty(hover)) {
addHover(hosts, devices, hover);
}
sendMessage(flowSummaryMessage(sid, devices));
}
// Requests related intents message.
private synchronized void requestRelatedIntents(ObjectNode event) {
ObjectNode payload = payload(event);
if (!payload.has("ids")) {
return;
}
long sid = number(event, "sid");
// Cancel any other traffic monitoring mode.
stopTrafficMonitoring();
// 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(sid, new TrafficClass("primary", selectedIntents)));
}
// FIXME: Re-introduce one 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 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(sid, new TrafficClass("primary", primary),
new TrafficClass("secondary", secondary)));
}
// Requests next or previous related intent.
private void requestAnotherRelatedIntent(ObjectNode event, int offset) {
if (haveSelectedIntents()) {
currentIntentIndex = currentIntentIndex + offset;
if (currentIntentIndex < 0) {
currentIntentIndex = selectedIntents.size() - 1;
} else if (currentIntentIndex >= selectedIntents.size()) {
currentIntentIndex = 0;
}
sendSelectedIntent(event);
}
}
// Sends traffic information on the related intents with the currently
// selected intent highlighted.
private void sendSelectedIntent(ObjectNode event) {
Intent selectedIntent = selectedIntents.get(currentIntentIndex);
log.info("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(number(event, "sid"),
new TrafficClass("primary", primary),
new TrafficClass("secondary", secondary)));
}
// Requests monitoring of traffic for the selected intent.
private void requestSelectedIntentTraffic(ObjectNode event) {
if (haveSelectedIntents()) {
if (currentIntentIndex < 0) {
currentIntentIndex = 0;
}
Intent selectedIntent = selectedIntents.get(currentIntentIndex);
log.info("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(number(event, "sid"),
new TrafficClass("primary", primary, true)));
}
}
// Cancels sending traffic messages.
private void cancelTraffic(ObjectNode event) {
selectedIntents = null;
sendMessage(trafficMessage(number(event, "sid")));
stopTrafficMonitoring();
}
private synchronized long startSummaryMonitoring(ObjectNode event) {
stopSummaryMonitoring();
summaryEvent = event;
summaryTask = new SummaryMonitor();
timer.schedule(summaryTask, SUMMARY_FREQUENCY, SUMMARY_FREQUENCY);
return number(event, "sid");
}
private synchronized void stopSummaryMonitoring() {
if (summaryEvent != null) {
summaryTask.cancel();
summaryTask = null;
summaryEvent = null;
}
}
// Subscribes for summary messages.
private synchronized void requestSummary(ObjectNode event) {
sendMessage(summmaryMessage(number(event, "sid")));
}
// Forces mastership role rebalancing.
private void equalizeMasters(ObjectNode event) {
directory.get(MastershipAdminService.class).balanceRoles();
}
// Adds all internal listeners.
private void addListeners() {
clusterService.addListener(clusterListener);
mastershipService.addListener(mastershipListener);
deviceService.addListener(deviceListener);
linkService.addListener(linkListener);
hostService.addListener(hostListener);
intentService.addListener(intentListener);
flowService.addListener(flowListener);
}
// Removes all internal listeners.
private synchronized void removeListeners() {
if (!listenersRemoved) {
listenersRemoved = true;
clusterService.removeListener(clusterListener);
mastershipService.removeListener(mastershipListener);
deviceService.removeListener(deviceListener);
linkService.removeListener(linkListener);
hostService.removeListener(hostListener);
intentService.removeListener(intentListener);
flowService.removeListener(flowListener);
}
}
// Cluster event listener.
private class InternalClusterListener implements ClusterEventListener {
@Override
public void event(ClusterEvent event) {
sendMessage(instanceMessage(event, null));
}
}
// Mastership change listener
private class InternalMastershipListener implements MastershipListener {
@Override
public void event(MastershipEvent event) {
sendAllInstances("updateInstance");
Device device = deviceService.getDevice(event.subject());
sendMessage(deviceMessage(new DeviceEvent(DEVICE_UPDATED, device)));
}
}
// Device event listener.
private class InternalDeviceListener implements DeviceListener {
@Override
public void event(DeviceEvent event) {
sendMessage(deviceMessage(event));
eventAccummulator.add(event);
}
}
// Link event listener.
private class InternalLinkListener implements LinkListener {
@Override
public void event(LinkEvent event) {
sendMessage(linkMessage(event));
eventAccummulator.add(event);
}
}
// Host event listener.
private class InternalHostListener implements HostListener {
@Override
public void event(HostEvent event) {
sendMessage(hostMessage(event));
eventAccummulator.add(event);
}
}
// Intent event listener.
private class InternalIntentListener implements IntentListener {
@Override
public void event(IntentEvent event) {
if (trafficEvent != null) {
requestSelectedIntentTraffic(trafficEvent);
}
eventAccummulator.add(event);
}
}
// Intent event listener.
private class InternalFlowListener implements FlowRuleListener {
@Override
public void event(FlowRuleEvent event) {
eventAccummulator.add(event);
}
}
// Periodic update of the traffic information
private class TrafficMonitor extends TimerTask {
@Override
public void run() {
try {
if (trafficEvent != null) {
String type = string(trafficEvent, "event", "unknown");
if (type.equals("requestAllTraffic")) {
requestAllTraffic(trafficEvent);
} else if (type.equals("requestDeviceLinkFlows")) {
requestDeviceLinkFlows(trafficEvent);
} else if (type.equals("requestSelectedIntentTraffic")) {
requestSelectedIntentTraffic(trafficEvent);
}
}
} catch (Exception e) {
log.warn("Unable to handle traffic request due to {}", e.getMessage());
log.warn("Boom!", e);
}
}
}
// Periodic update of the summary information
private class SummaryMonitor extends TimerTask {
@Override
public void run() {
try {
if (summaryEvent != null) {
requestSummary(summaryEvent);
}
} catch (Exception e) {
log.warn("Unable to handle summary request due to {}", e.getMessage());
log.warn("Boom!", e);
}
}
}
// Accumulates events to drive methodic update of the summary pane.
private class InternalEventAccummulator extends AbstractAccumulator<Event> {
protected InternalEventAccummulator() {
super(new Timer("topo-summary"), MAX_EVENTS, MAX_BATCH_MS, MAX_IDLE_MS);
}
@Override
public void processItems(List<Event> items) {
try {
if (summaryEvent != null) {
sendMessage(summmaryMessage(0));
}
} catch (Exception e) {
log.warn("Unable to handle summary request due to {}", e.getMessage());
log.debug("Boom!", e);
}
}
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.ui.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onlab.osgi.ServiceDirectory;
import org.onlab.packet.IpAddress;
import org.onosproject.cluster.ClusterEvent;
import org.onosproject.cluster.ClusterService;
import org.onosproject.cluster.ControllerNode;
import org.onosproject.cluster.NodeId;
import org.onosproject.core.CoreService;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.Annotated;
import org.onosproject.net.AnnotationKeys;
import org.onosproject.net.Annotations;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultEdgeLink;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.EdgeLink;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.HostLocation;
import org.onosproject.net.Link;
import org.onosproject.net.LinkKey;
import org.onosproject.net.PortNumber;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.FlowRuleService;
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.host.HostEvent;
import org.onosproject.net.host.HostService;
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.UiConnection;
import org.onosproject.ui.UiMessageHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
import static org.onosproject.net.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;
import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
/**
* Facility for creating messages bound for the topology viewer.
*/
public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
protected static final Logger log = LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
private static final ProviderId PID = new ProviderId("core", "org.onosproject.core", true);
private static final String COMPACT = "%s/%s-%s/%s";
private static final double KB = 1024;
private static final double MB = 1024 * KB;
private static final double GB = 1024 * MB;
private static final String GB_UNIT = "GB";
private static final String MB_UNIT = "MB";
private static final String KB_UNIT = "KB";
private static final String B_UNIT = "B";
protected ServiceDirectory directory;
protected ClusterService clusterService;
protected DeviceService deviceService;
protected LinkService linkService;
protected HostService hostService;
protected MastershipService mastershipService;
protected IntentService intentService;
protected FlowRuleService flowService;
protected StatisticService statService;
protected TopologyService topologyService;
protected final ObjectMapper mapper = new ObjectMapper();
private String version;
// TODO: extract into an external & durable state; good enough for now and demo
private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
/**
* Creates a new message handler for the specified set of message types.
*
* @param messageTypes set of message types
*/
protected TopologyViewMessageHandlerBase(Set<String> messageTypes) {
super(messageTypes);
}
/**
* Returns read-only view of the meta-ui information.
*
* @return map of id to meta-ui mementos
*/
static Map<String, ObjectNode> getMetaUi() {
return Collections.unmodifiableMap(metaUi);
}
@Override
public void init(UiConnection connection, ServiceDirectory directory) {
super.init(connection, directory);
this.directory = checkNotNull(directory, "Directory cannot be null");
clusterService = directory.get(ClusterService.class);
deviceService = directory.get(DeviceService.class);
linkService = directory.get(LinkService.class);
hostService = directory.get(HostService.class);
mastershipService = directory.get(MastershipService.class);
intentService = directory.get(IntentService.class);
flowService = directory.get(FlowRuleService.class);
statService = directory.get(StatisticService.class);
topologyService = directory.get(TopologyService.class);
String ver = directory.get(CoreService.class).version().toString();
version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
}
// Retrieves the payload from the specified event.
protected ObjectNode payload(ObjectNode event) {
return (ObjectNode) event.path("payload");
}
// Returns the specified node property as a number
protected long number(ObjectNode node, String name) {
return node.path(name).asLong();
}
// Returns the specified node property as a string.
protected String string(ObjectNode node, String name) {
return node.path(name).asText();
}
// Returns the specified node property as a string.
protected String string(ObjectNode node, String name, String defaultValue) {
return node.path(name).asText(defaultValue);
}
// Returns the specified set of IP addresses as a string.
private String ip(Set<IpAddress> ipAddresses) {
Iterator<IpAddress> it = ipAddresses.iterator();
return it.hasNext() ? it.next().toString() : "unknown";
}
// Produces JSON structure from annotations.
private JsonNode props(Annotations annotations) {
ObjectNode props = mapper.createObjectNode();
if (annotations != null) {
for (String key : annotations.keys()) {
props.put(key, annotations.value(key));
}
}
return props;
}
// Produces an informational log message event bound to the client.
protected ObjectNode info(long id, String message) {
return message("info", id, message);
}
// Produces a warning log message event bound to the client.
protected ObjectNode warning(long id, String message) {
return message("warning", id, message);
}
// Produces an error log message event bound to the client.
protected ObjectNode error(long id, String message) {
return message("error", id, message);
}
// Produces a log message event bound to the client.
private ObjectNode message(String severity, long id, String message) {
return envelope("message", id,
mapper.createObjectNode()
.put("severity", severity)
.put("message", message));
}
// Puts the payload into an envelope and returns it.
protected ObjectNode envelope(String type, long sid, ObjectNode payload) {
ObjectNode event = mapper.createObjectNode();
event.put("event", type);
if (sid > 0) {
event.put("sid", sid);
}
event.set("payload", payload);
return event;
}
// 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();
int switchCount = mastershipService.getDevicesOf(node.id()).size();
ObjectNode payload = mapper.createObjectNode()
.put("id", node.id().toString())
.put("ip", node.ip().toString())
.put("online", clusterService.getState(node.id()) == ACTIVE)
.put("uiAttached", event.subject().equals(clusterService.getLocalNode()))
.put("switches", switchCount);
ArrayNode labels = mapper.createArrayNode();
labels.add(node.id().toString());
labels.add(node.ip().toString());
// Add labels, props and stuff the payload into envelope.
payload.set("labels", labels);
addMetaUi(node.id().toString(), payload);
String type = messageType != null ? messageType :
((event.type() == INSTANCE_ADDED) ? "addInstance" :
((event.type() == INSTANCE_REMOVED ? "removeInstance" :
"addInstance")));
return envelope(type, 0, payload);
}
// Produces a device event message to the client.
protected ObjectNode deviceMessage(DeviceEvent event) {
Device device = event.subject();
ObjectNode payload = mapper.createObjectNode()
.put("id", device.id().toString())
.put("type", device.type().toString().toLowerCase())
.put("online", deviceService.isAvailable(device.id()))
.put("master", master(device.id()));
// Generate labels: id, chassis id, no-label, optional-name
String name = device.annotations().value(AnnotationKeys.NAME);
ArrayNode labels = mapper.createArrayNode();
labels.add("");
labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
labels.add(device.id().toString());
// Add labels, props and stuff the payload into envelope.
payload.set("labels", labels);
payload.set("props", props(device.annotations()));
addGeoLocation(device, payload);
addMetaUi(device.id().toString(), payload);
String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
return envelope(type, 0, payload);
}
// Produces a link event message to the client.
protected ObjectNode linkMessage(LinkEvent event) {
Link link = event.subject();
ObjectNode payload = mapper.createObjectNode()
.put("id", compactLinkString(link))
.put("type", link.type().toString().toLowerCase())
.put("online", link.state() == Link.State.ACTIVE)
.put("linkWidth", 1.2)
.put("src", link.src().deviceId().toString())
.put("srcPort", link.src().port().toString())
.put("dst", link.dst().deviceId().toString())
.put("dstPort", link.dst().port().toString());
String type = (event.type() == LINK_ADDED) ? "addLink" :
((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
return envelope(type, 0, payload);
}
// Produces a host event message to the client.
protected ObjectNode hostMessage(HostEvent event) {
Host host = event.subject();
String hostType = host.annotations().value(AnnotationKeys.TYPE);
ObjectNode payload = mapper.createObjectNode()
.put("id", host.id().toString())
.put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
.put("ingress", compactLinkString(edgeLink(host, true)))
.put("egress", compactLinkString(edgeLink(host, false)));
payload.set("cp", hostConnect(mapper, host.location()));
payload.set("labels", labels(mapper, ip(host.ipAddresses()),
host.mac().toString()));
payload.set("props", props(host.annotations()));
addGeoLocation(host, payload);
addMetaUi(host.id().toString(), payload);
String type = (event.type() == HOST_ADDED) ? "addHost" :
((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
return envelope(type, 0, payload);
}
// Encodes the specified host location into a JSON object.
private ObjectNode hostConnect(ObjectMapper mapper, HostLocation location) {
return mapper.createObjectNode()
.put("device", location.deviceId().toString())
.put("port", location.port().toLong());
}
// Encodes the specified list of labels a JSON array.
private ArrayNode labels(ObjectMapper mapper, String... labels) {
ArrayNode json = mapper.createArrayNode();
for (String label : labels) {
json.add(label);
}
return json;
}
// Returns the name of the master node for the specified device id.
private String master(DeviceId deviceId) {
NodeId master = mastershipService.getMasterFor(deviceId);
return master != null ? master.toString() : "";
}
// Generates an edge link from the specified host location.
private EdgeLink edgeLink(Host host, boolean ingress) {
return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
host.location(), ingress);
}
// Adds meta UI information for the specified object.
private void addMetaUi(String id, ObjectNode payload) {
ObjectNode meta = metaUi.get(id);
if (meta != null) {
payload.set("metaUi", meta);
}
}
// Adds a geo location JSON to the specified payload object.
private void addGeoLocation(Annotated annotated, ObjectNode payload) {
Annotations annotations = annotated.annotations();
if (annotations == null) {
return;
}
String slat = annotations.value(AnnotationKeys.LATITUDE);
String slng = annotations.value(AnnotationKeys.LONGITUDE);
try {
if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
double lat = Double.parseDouble(slat);
double lng = Double.parseDouble(slng);
ObjectNode loc = mapper.createObjectNode()
.put("type", "latlng").put("lat", lat).put("lng", lng);
payload.set("location", loc);
}
} catch (NumberFormatException e) {
log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
}
}
// Updates meta UI information for the specified object.
protected void updateMetaUi(ObjectNode event) {
ObjectNode payload = payload(event);
metaUi.put(string(payload, "id"), (ObjectNode) payload.path("memento"));
}
// Returns summary response.
protected ObjectNode summmaryMessage(long sid) {
Topology topology = topologyService.currentTopology();
return envelope("showSummary", sid,
json("ONOS Summary", "node",
new Prop("Devices", format(topology.deviceCount())),
new Prop("Links", format(topology.linkCount())),
new Prop("Hosts", format(hostService.getHostCount())),
new Prop("Topology SCCs", format(topology.clusterCount())),
new Separator(),
new Prop("Intents", format(intentService.getIntentCount())),
new Prop("Flows", format(flowService.getFlowRuleCount())),
new Prop("Version", version)));
}
// Returns device details response.
protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
Device device = deviceService.getDevice(deviceId);
Annotations annot = device.annotations();
String name = annot.value(AnnotationKeys.NAME);
int portCount = deviceService.getPorts(deviceId).size();
int flowCount = getFlowCount(deviceId);
return envelope("showDetails", sid,
json(isNullOrEmpty(name) ? deviceId.toString() : name,
device.type().toString().toLowerCase(),
new Prop("URI", deviceId.toString()),
new Prop("Vendor", device.manufacturer()),
new Prop("H/W Version", device.hwVersion()),
new Prop("S/W Version", device.swVersion()),
new Prop("Serial Number", device.serialNumber()),
new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
new Separator(),
new Prop("Master", master(deviceId)),
new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
new Separator(),
new Prop("Ports", Integer.toString(portCount)),
new Prop("Flows", Integer.toString(flowCount))));
}
protected int getFlowCount(DeviceId deviceId) {
int count = 0;
Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
while (it.hasNext()) {
count++;
it.next();
}
return count;
}
// Counts all entries that egress on the given device links.
protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
List<FlowEntry> entries = new ArrayList<>();
Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
Set<Host> hosts = hostService.getConnectedHosts(deviceId);
Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
while (it.hasNext()) {
entries.add(it.next());
}
// Add all edge links to the set
if (hosts != null) {
for (Host host : hosts) {
links.add(new DefaultEdgeLink(host.providerId(),
new ConnectPoint(host.id(), P0),
host.location(), false));
}
}
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 Integer 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.instructions()) {
if (instruction.type() == Instruction.Type.OUTPUT &&
((OutputInstruction) instruction).port().equals(out)) {
count++;
}
}
}
return count;
}
// Returns host details response.
protected ObjectNode hostDetails(HostId hostId, long sid) {
Host host = hostService.getHost(hostId);
Annotations annot = host.annotations();
String type = annot.value(AnnotationKeys.TYPE);
String name = annot.value(AnnotationKeys.NAME);
String vlan = host.vlan().toString();
return envelope("showDetails", sid,
json(isNullOrEmpty(name) ? hostId.toString() : name,
isNullOrEmpty(type) ? "endstation" : type,
new Prop("MAC", host.mac().toString()),
new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
new Separator(),
new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
}
// Produces JSON message to trigger traffic overview visualization
protected ObjectNode trafficSummaryMessage(long sid) {
ObjectNode payload = mapper.createObjectNode();
ArrayNode paths = mapper.createArrayNode();
payload.set("paths", paths);
ObjectNode pathNodeN = mapper.createObjectNode();
ArrayNode linksNodeN = mapper.createArrayNode();
ArrayNode labelsN = mapper.createArrayNode();
pathNodeN.put("class", "plain").put("traffic", false);
pathNodeN.set("links", linksNodeN);
pathNodeN.set("labels", labelsN);
paths.add(pathNodeN);
ObjectNode pathNodeT = mapper.createObjectNode();
ArrayNode linksNodeT = mapper.createArrayNode();
ArrayNode labelsT = mapper.createArrayNode();
pathNodeT.put("class", "secondary").put("traffic", true);
pathNodeT.set("links", linksNodeT);
pathNodeT.set("labels", labelsT);
paths.add(pathNodeT);
for (BiLink link : consolidateLinks(linkService.getLinks())) {
boolean bi = link.two != null;
if (isInfrastructureEgress(link.one) ||
(bi && isInfrastructureEgress(link.two))) {
link.addLoad(statService.load(link.one));
link.addLoad(bi ? statService.load(link.two) : null);
if (link.hasTraffic) {
linksNodeT.add(compactLinkString(link.one));
labelsT.add(formatBytes(link.bytes));
} else {
linksNodeN.add(compactLinkString(link.one));
labelsN.add("");
}
}
}
return envelope("showTraffic", sid, payload);
}
private Collection<BiLink> consolidateLinks(Iterable<Link> links) {
Map<LinkKey, BiLink> biLinks = new HashMap<>();
for (Link link : links) {
addLink(biLinks, link);
}
return biLinks.values();
}
// Produces JSON message to trigger flow overview visualization
protected ObjectNode flowSummaryMessage(long sid, Set<Device> devices) {
ObjectNode payload = mapper.createObjectNode();
ArrayNode paths = mapper.createArrayNode();
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 envelope("showTraffic", sid, payload);
}
private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
ObjectNode pathNode = mapper.createObjectNode();
ArrayNode linksNode = mapper.createArrayNode();
ArrayNode labels = mapper.createArrayNode();
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);
}
// Produces JSON message to trigger traffic visualization
protected ObjectNode trafficMessage(long sid, TrafficClass... trafficClasses) {
ObjectNode payload = mapper.createObjectNode();
ArrayNode paths = mapper.createArrayNode();
payload.set("paths", paths);
// Classify links based on their traffic traffic first...
Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
// Then separate the links into their respective classes and send them out.
Map<String, ObjectNode> pathNodes = new HashMap<>();
for (BiLink biLink : biLinks.values()) {
boolean hasTraffic = biLink.hasTraffic;
String tc = (biLink.classes + (hasTraffic ? " animated" : "")).trim();
ObjectNode pathNode = pathNodes.get(tc);
if (pathNode == null) {
pathNode = mapper.createObjectNode()
.put("class", tc).put("traffic", hasTraffic);
pathNode.set("links", mapper.createArrayNode());
pathNode.set("labels", mapper.createArrayNode());
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 envelope("showTraffic", sid, 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 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;
}
// 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 (isInfrastructureEgress(link)) {
if (showTraffic) {
biLink.addLoad(statService.load(link));
}
biLink.addClass(type);
}
}
}
}
private 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;
}
// Adds the link segments (path or tree) associated with the specified
// connectivity intent
protected void addPathTraffic(ArrayNode paths, String type, String trafficType,
Iterable<Link> links) {
ObjectNode pathNode = mapper.createObjectNode();
ArrayNode linksNode = mapper.createArrayNode();
if (links != null) {
ArrayNode labels = mapper.createArrayNode();
boolean hasTraffic = false;
for (Link link : links) {
if (isInfrastructureEgress(link)) {
linksNode.add(compactLinkString(link));
Load load = statService.load(link);
String label = "";
if (load.rate() > 0) {
hasTraffic = true;
label = formatBytes(load.latest());
}
labels.add(label);
}
}
pathNode.put("class", hasTraffic ? type + " " + trafficType : type);
pathNode.put("traffic", hasTraffic);
pathNode.set("links", linksNode);
pathNode.set("labels", labels);
paths.add(pathNode);
}
}
// Poor-mans formatting to get the labels with byte counts looking nice.
private String formatBytes(long bytes) {
String unit;
double value;
if (bytes > GB) {
value = bytes / GB;
unit = GB_UNIT;
} else if (bytes > MB) {
value = bytes / MB;
unit = MB_UNIT;
} else if (bytes > KB) {
value = bytes / KB;
unit = KB_UNIT;
} else {
value = bytes;
unit = B_UNIT;
}
DecimalFormat format = new DecimalFormat("#,###.##");
return format.format(value) + " " + unit;
}
// Formats the given number into a string.
private String format(Number number) {
DecimalFormat format = new DecimalFormat("#,###");
return format.format(number);
}
private boolean isInfrastructureEgress(Link link) {
return link.src().elementId() instanceof DeviceId;
}
// 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());
}
// Produces JSON property details.
private ObjectNode json(String id, String type, Prop... props) {
ObjectMapper mapper = new ObjectMapper();
ObjectNode result = mapper.createObjectNode()
.put("id", id).put("type", type);
ObjectNode pnode = mapper.createObjectNode();
ArrayNode porder = mapper.createArrayNode();
for (Prop p : props) {
porder.add(p.key);
pnode.put(p.key, p.value);
}
result.set("propOrder", porder);
result.set("props", pnode);
return result;
}
// Produces canonical link key, i.e. one that will match link and its inverse.
private 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.
private class BiLink {
public final LinkKey key;
public final Link one;
public Link two;
public boolean hasTraffic = false;
public long bytes = 0;
public String classes = "";
BiLink(LinkKey key, Link link) {
this.key = key;
this.one = link;
}
void setOther(Link link) {
this.two = link;
}
void addLoad(Load load) {
if (load != null) {
this.hasTraffic = hasTraffic || load.rate() > 0;
this.bytes += load.latest();
}
}
void addClass(String trafficClass) {
classes = classes + " " + trafficClass;
}
}
// Auxiliary key/value carrier.
private class Prop {
public final String key;
public final String value;
protected Prop(String key, String value) {
this.key = key;
this.value = value;
}
}
// Auxiliary properties separator
private class Separator extends Prop {
protected Separator() {
super("-", "");
}
}
// Auxiliary carrier of data for requesting traffic message.
protected 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;
}
}
}
......@@ -98,6 +98,7 @@ import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
/**
* Facility for creating messages bound for the topology viewer.
*/
@Deprecated
public abstract class TopologyViewMessages {
protected static final Logger log = LoggerFactory.getLogger(TopologyViewMessages.class);
......
......@@ -77,6 +77,7 @@ import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
/**
* Web socket capable of interacting with the GUI topology view.
*/
@Deprecated
public class TopologyViewWebSocket
extends TopologyViewMessages
implements WebSocket.OnTextMessage, WebSocket.OnControl {
......
......@@ -59,7 +59,7 @@ public class UiExtensionManager implements UiExtensionService {
List<UiView> coreViews = of(new UiView("sample", "Sample"),
new UiView("topo", "Topology View"),
new UiView("device", "Devices"));
UiMessageHandlerFactory messageHandlerFactory = null;
UiMessageHandlerFactory messageHandlerFactory = () -> ImmutableList.of(new TopologyViewMessageHandler());
return new UiExtension(coreViews, messageHandlerFactory, "core",
UiExtensionManager.class.getClassLoader());
}
......
......@@ -22,6 +22,7 @@ import org.onlab.osgi.ServiceDirectory;
import org.onosproject.ui.UiConnection;
import org.onosproject.ui.UiExtensionService;
import org.onosproject.ui.UiMessageHandler;
import org.onosproject.ui.UiMessageHandlerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -117,7 +118,7 @@ public class UiWebSocket
lastActive = System.currentTimeMillis();
try {
ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
String type = message.path("type").asText("unknown");
String type = message.path("event").asText("unknown");
UiMessageHandler handler = handlers.get(type);
if (handler != null) {
handler.process(message);
......@@ -146,10 +147,15 @@ public class UiWebSocket
private void createHandlers() {
handlers = new HashMap<>();
UiExtensionService service = directory.get(UiExtensionService.class);
service.getExtensions().forEach(ext -> ext.messageHandlerFactory().newHandlers().forEach(handler -> {
handler.init(this, directory);
handler.messageTypes().forEach(type -> handlers.put(type, handler));
}));
service.getExtensions().forEach(ext -> {
UiMessageHandlerFactory factory = ext.messageHandlerFactory();
if (factory != null) {
factory.newHandlers().forEach(handler -> {
handler.init(this, directory);
handler.messageTypes().forEach(type -> handlers.put(type, handler));
});
}
});
}
// Destroys message handlers.
......
......@@ -151,12 +151,24 @@
<servlet>
<servlet-name>Web Socket Service</servlet-name>
<servlet-class>org.onosproject.ui.impl.GuiWebSocketServlet</servlet-class>
<servlet-class>org.onosproject.ui.impl.UiWebSocketServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Web Socket Service</servlet-name>
<url-pattern>/websock/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Legacy Web Socket Service</servlet-name>
<servlet-class>org.onosproject.ui.impl.GuiWebSocketServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Legacy Web Socket Service</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
......
......@@ -22,7 +22,7 @@
var uiContext = '/onos/ui/',
rsSuffix = uiContext + 'rs/',
wsSuffix = uiContext + 'ws/';
wsSuffix = uiContext + 'websock/';
angular.module('onosRemote')
.factory('UrlFnService', ['$location', function ($loc) {
......
......@@ -20,62 +20,105 @@
(function () {
'use strict';
var fs;
// injected refs
var fs, $log;
function fnOpen(f) {
// wrap the onOpen function; we will handle any housekeeping here...
if (!fs.isF(f)) {
return null;
// internal state
var ws, sws, sid = 0,
handlers = {};
function resetSid() {
sid = 0;
}
// Binds the specified message handlers.
function bindHandlers(handlerMap) {
var m = d3.map(handlerMap),
dups = [];
m.forEach(function (key, value) {
var fn = fs.isF(value[key]);
if (!fn) {
$log.warn(key + ' binding not a function on ' + value);
return;
}
if (handlers[key]) {
dups.push(key);
} else {
handlers[key] = fn;
}
});
if (dups.length) {
$log.warn('duplicate bindings ignored:', dups);
}
}
return function (openEvent) {
// NOTE: nothing worth passing to the caller?
f();
};
// Unbinds the specified message handlers.
function unbindHandlers(handlerMap) {
var m = d3.map(handlerMap);
m.forEach(function (key) {
delete handlers[key];
});
}
function fnMessage(f) {
// wrap the onMessage function; we will attempt to decode the
// message event payload as JSON and pass that in...
if (!fs.isF(f)) {
return null;
// Formulates an event message and sends it via the shared web-socket.
function sendEvent(evType, payload) {
var p = payload || {};
if (sws) {
$log.debug(' *Tx* >> ', evType, payload);
sws.send({
event: evType,
sid: ++sid,
payload: p
});
} else {
$log.warn('sendEvent: no websocket open:', evType, payload);
}
}
return function (msgEvent) {
var ev;
try {
ev = JSON.parse(msgEvent.data);
} catch (e) {
ev = {
error: 'Failed to parse JSON',
e: e
};
}
f(ev);
};
// Handles the specified message using handler bindings.
function handleMessage(msgEvent) {
var ev;
try {
ev = JSON.parse(msgEvent.data);
$log.debug(' *Rx* >> ', ev.event, ev.payload);
dispatchToHandler(ev);
} catch (e) {
$log.error('message is not valid JSON', msgEvent);
}
}
function fnClose(f) {
// wrap the onClose function; we will handle any parameters to the
// close event here...
if (!fs.isF(f)) {
return null;
// Dispatches the message to the appropriate handler.
function dispatchToHandler(event) {
var handler = handlers[event.event];
if (handler) {
handler(event.payload);
} else {
$log.warn('unhandled event:', event);
}
}
function handleOpen() {
$log.info('web socket open');
// FIXME: implement calling external hooks
}
return function (closeEvent) {
// NOTE: only seen {reason == ""} so far, nevertheless...
f(closeEvent.reason);
};
function handleClose() {
$log.info('web socket closed');
// FIXME: implement reconnect logic
}
angular.module('onosRemote')
.factory('WebSocketService',
['$log', '$location', 'UrlFnService', 'FnService',
function ($log, $loc, ufs, _fs_) {
function (_$log_, $loc, ufs, _fs_) {
fs = _fs_;
$log = _$log_;
// creates a web socket for the given path, returning a "handle".
// Creates a web socket for the given path, returning a "handle".
// opts contains the event handler callbacks, etc.
function createWebSocket(path, opts) {
var o = opts || {},
......@@ -85,8 +128,7 @@
meta: { path: fullUrl, ws: null },
send: send,
close: close
},
ws;
};
try {
ws = new WebSocket(fullUrl);
......@@ -97,23 +139,21 @@
$log.debug('Attempting to open websocket to: ' + fullUrl);
if (ws) {
ws.onopen = fnOpen(o.onOpen);
ws.onmessage = fnMessage(o.onMessage);
ws.onclose = fnClose(o.onClose);
ws.onopen = handleOpen;
ws.onmessage = handleMessage;
ws.onclose = handleClose;
}
// messages are expected to be event objects..
// Sends a formulated event message via the backing web-socket.
function send(ev) {
if (ev) {
if (ws) {
ws.send(JSON.stringify(ev));
} else {
$log.warn('ws.send() no web socket open!',
fullUrl, ev);
}
if (ev && ws) {
ws.send(JSON.stringify(ev));
} else if (!ws) {
$log.warn('ws.send() no web socket open!', fullUrl, ev);
}
}
// Closes the backing web-socket.
function close() {
if (ws) {
ws.close();
......@@ -122,11 +162,16 @@
}
}
sws = api; // Make the shared web-socket accessible
return api;
}
return {
createWebSocket: createWebSocket
resetSid: resetSid,
createWebSocket: createWebSocket,
bindHandlers: bindHandlers,
unbindHandlers: unbindHandlers,
sendEvent: sendEvent
};
}]);
......
......@@ -15,6 +15,7 @@
*/
/*
DEPRECATED: to be deleted
ONOS GUI -- Remote -- Web Socket Event Service
*/
(function () {
......
......@@ -263,7 +263,7 @@
// Cleanup on destroyed scope..
$scope.$on('$destroy', function () {
$log.log('OvTopoCtrl is saying Buh-Bye!');
tes.closeSock();
tes.stop();
tps.destroyPanels();
tis.destroyInst();
tfs.destroyForce();
......@@ -291,7 +291,7 @@
tfs.initForce(svg, forceG, uplink, dim);
tis.initInst({ showMastership: tfs.showMastership });
tps.initPanels({ sendEvent: tes.sendEvent });
tes.openSock();
tes.start();
$log.log('OvTopoCtrl has been created');
}]);
......
......@@ -27,15 +27,15 @@
'use strict';
// injected refs
var $log, wss, wes, vs, tps, tis, tfs, tss, tts;
var $log, vs, wss, tps, tis, tfs, tss, tts;
// internal state
var wsock, evApis;
var handlers;
// ==========================
function bindApis() {
evApis = {
function createHandlers() {
handlers = {
showSummary: tps,
showDetails: tss,
......@@ -58,103 +58,43 @@
};
}
var nilApi = {},
dispatcher = {
handleEvent: function (ev) {
var eid = ev.event,
api = evApis[eid] || nilApi,
eh = api[eid];
if (eh) {
$log.debug(' << *Rx* ', eid, ev.payload);
eh(ev.payload);
} else {
$log.warn('Unknown event (ignored):', ev);
}
},
sendEvent: function (evType, payload) {
if (wsock) {
$log.debug(' *Tx* >> ', evType, payload);
wes.sendEvent(wsock, evType, payload);
} else {
$log.warn('sendEvent: no websocket open:', evType, payload);
}
}
};
// === Web Socket functions ===
function onWsOpen() {
$log.debug('web socket opened...');
// start by requesting periodic summary data...
dispatcher.sendEvent('requestSummary');
vs.hide();
}
function onWsMessage(ev) {
dispatcher.handleEvent(ev);
}
function onWsClose(reason) {
$log.log('web socket closed; reason=', reason);
wsock = null;
vs.lostServer('OvTopoCtrl', [
'Oops!',
'Web-socket connection to server closed...',
'Try refreshing the page.'
]);
}
// ==========================
var nilApi = {};
angular.module('ovTopo')
.factory('TopoEventService',
['$log', '$location', 'WebSocketService', 'WsEventService', 'VeilService',
['$log', '$location', 'VeilService', 'WebSocketService',
'TopoPanelService', 'TopoInstService', 'TopoForceService',
'TopoSelectService', 'TopoTrafficService',
function (_$log_, $loc, _wss_, _wes_, _vs_,
_tps_, _tis_, _tfs_, _tss_, _tts_) {
function (_$log_, $loc, _vs_, _wss_, _tps_, _tis_, _tfs_, _tss_, _tts_) {
$log = _$log_;
wss = _wss_;
wes = _wes_;
vs = _vs_;
wss = _wss_;
tps = _tps_;
tis = _tis_;
tfs = _tfs_;
tss = _tss_;
tts = _tts_;
bindApis();
// TODO: handle "guiSuccessor" functionality (replace host)
// TODO: implement retry on close functionality
createHandlers();
function openSock() {
wsock = wss.createWebSocket('topology', {
onOpen: onWsOpen,
onMessage: onWsMessage,
onClose: onWsClose,
wsport: $loc.search().wsport
});
$log.debug('web socket opened:', wsock);
// FIXME: need to handle async socket open to avoid race
function start() {
wss.bindHandlers(handlers);
wss.sendEvent('topoStart');
$log.debug('topo comms started');
}
function closeSock() {
var path;
if (wsock) {
path = wsock.meta.path;
wsock.close();
wsock = null;
$log.debug('web socket closed. path:', path);
}
function stop() {
wss.unbindHandlers();
wss.sendEvent('topoStop');
$log.debug('topo comms stopped');
}
return {
openSock: openSock,
closeSock: closeSock,
sendEvent: dispatcher.sendEvent
start: start,
stop: stop,
sendEvent: wss.sendEvent
};
}]);
}());
......
......@@ -64,10 +64,10 @@
.controller('OnosCtrl', [
'$log', '$route', '$routeParams', '$location',
'KeyService', 'ThemeService', 'GlyphService', 'PanelService',
'FlashService', 'QuickHelpService',
'FlashService', 'QuickHelpService', 'WebSocketService',
function ($log, $route, $routeParams, $location,
ks, ts, gs, ps, flash, qhs) {
ks, ts, gs, ps, flash, qhs, wss) {
var self = this;
self.$route = $route;
......@@ -84,6 +84,13 @@
flash.initFlash();
qhs.initQuickHelp();
// TODO: register handlers for initial messages: instances, settings, etc.
// TODO: opts?
wss.createWebSocket('core', {
wsport: $location.search().wsport
});
$log.log('OnosCtrl has been created');
$log.debug('route: ', self.$route);
......