Deprecating old web-socket stuff and adding ability for client-side message handler registration. Failover still to be done and same for the async hooks.
Change-Id: I6029c91eb1a04e01401e495b9673ddaea728e215
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/GuiWebSocketServlet.java b/web/gui/src/main/java/org/onosproject/ui/impl/GuiWebSocketServlet.java
index 5a660e0..bc105df 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/GuiWebSocketServlet.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/GuiWebSocketServlet.java
@@ -31,6 +31,7 @@
/**
* 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;
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
new file mode 100644
index 0000000..29505b7
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
@@ -0,0 +1,705 @@
+/*
+ * 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);
+ }
+ }
+ }
+}
+
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
new file mode 100644
index 0000000..9e8fcc7
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
@@ -0,0 +1,892 @@
+/*
+ * 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;
+ }
+ }
+
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessages.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessages.java
index dd891cd..b9b50ca 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessages.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessages.java
@@ -98,6 +98,7 @@
/**
* Facility for creating messages bound for the topology viewer.
*/
+@Deprecated
public abstract class TopologyViewMessages {
protected static final Logger log = LoggerFactory.getLogger(TopologyViewMessages.class);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewWebSocket.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewWebSocket.java
index e259cb5..24e8b82 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewWebSocket.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewWebSocket.java
@@ -77,6 +77,7 @@
/**
* Web socket capable of interacting with the GUI topology view.
*/
+@Deprecated
public class TopologyViewWebSocket
extends TopologyViewMessages
implements WebSocket.OnTextMessage, WebSocket.OnControl {
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 3b9156d..fa8df2e 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
@@ -59,7 +59,7 @@
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());
}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
index 9b11b42..99ca2e9 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
@@ -22,6 +22,7 @@
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 @@
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 @@
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.
diff --git a/web/gui/src/main/webapp/WEB-INF/web.xml b/web/gui/src/main/webapp/WEB-INF/web.xml
index 511ceac..5371354 100644
--- a/web/gui/src/main/webapp/WEB-INF/web.xml
+++ b/web/gui/src/main/webapp/WEB-INF/web.xml
@@ -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>
diff --git a/web/gui/src/main/webapp/app/fw/remote/urlfn.js b/web/gui/src/main/webapp/app/fw/remote/urlfn.js
index fe43267..c2addef 100644
--- a/web/gui/src/main/webapp/app/fw/remote/urlfn.js
+++ b/web/gui/src/main/webapp/app/fw/remote/urlfn.js
@@ -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) {
diff --git a/web/gui/src/main/webapp/app/fw/remote/websocket.js b/web/gui/src/main/webapp/app/fw/remote/websocket.js
index abe6025..3229250 100644
--- a/web/gui/src/main/webapp/app/fw/remote/websocket.js
+++ b/web/gui/src/main/webapp/app/fw/remote/websocket.js
@@ -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 = {};
- return function (openEvent) {
- // NOTE: nothing worth passing to the caller?
- f();
- };
+ function resetSid() {
+ sid = 0;
}
- 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;
- }
+ // Binds the specified message handlers.
+ function bindHandlers(handlerMap) {
+ var m = d3.map(handlerMap),
+ dups = [];
- return function (msgEvent) {
- var ev;
- try {
- ev = JSON.parse(msgEvent.data);
- } catch (e) {
- ev = {
- error: 'Failed to parse JSON',
- e: e
- };
+ m.forEach(function (key, value) {
+ var fn = fs.isF(value[key]);
+ if (!fn) {
+ $log.warn(key + ' binding not a function on ' + value);
+ return;
}
- f(ev);
- };
+
+ if (handlers[key]) {
+ dups.push(key);
+ } else {
+ handlers[key] = fn;
+ }
+ });
+ if (dups.length) {
+ $log.warn('duplicate bindings ignored:', dups);
+ }
}
- function fnClose(f) {
- // wrap the onClose function; we will handle any parameters to the
- // close event here...
- if (!fs.isF(f)) {
- return null;
- }
+ // Unbinds the specified message handlers.
+ function unbindHandlers(handlerMap) {
+ var m = d3.map(handlerMap);
+ m.forEach(function (key) {
+ delete handlers[key];
+ });
+ }
- return function (closeEvent) {
- // NOTE: only seen {reason == ""} so far, nevertheless...
- f(closeEvent.reason);
- };
+ // 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);
+ }
+ }
+
+
+ // 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);
+ }
+ }
+
+ // 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
+ }
+
+ 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
};
}]);
diff --git a/web/gui/src/main/webapp/app/fw/remote/wsevent.js b/web/gui/src/main/webapp/app/fw/remote/wsevent.js
index bf04b86..02d71b5 100644
--- a/web/gui/src/main/webapp/app/fw/remote/wsevent.js
+++ b/web/gui/src/main/webapp/app/fw/remote/wsevent.js
@@ -15,6 +15,7 @@
*/
/*
+ DEPRECATED: to be deleted
ONOS GUI -- Remote -- Web Socket Event Service
*/
(function () {
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index 4463936..3dd8d6e 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -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');
}]);
diff --git a/web/gui/src/main/webapp/app/view/topo/topoEvent.js b/web/gui/src/main/webapp/app/view/topo/topoEvent.js
index 099ba4e..10b6909 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoEvent.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoEvent.js
@@ -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();
+ createHandlers();
- // TODO: handle "guiSuccessor" functionality (replace host)
- // TODO: implement retry on close functionality
-
- 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
};
}]);
}());
diff --git a/web/gui/src/main/webapp/onos.js b/web/gui/src/main/webapp/onos.js
index 0f049e9..97f3d06 100644
--- a/web/gui/src/main/webapp/onos.js
+++ b/web/gui/src/main/webapp/onos.js
@@ -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);