GUI -- Further work on refactoring Topology View server side code. Still WIP...
- added topology client heartbeat.
- modified AbstractListenerRegistry to allow for extension.
Change-Id: Ib8ea6ad4ba34f5732d062f1c9ef545f105eb167b
diff --git a/core/api/src/main/java/org/onosproject/event/AbstractListenerRegistry.java b/core/api/src/main/java/org/onosproject/event/AbstractListenerRegistry.java
index 71b8ec7..38575a4 100644
--- a/core/api/src/main/java/org/onosproject/event/AbstractListenerRegistry.java
+++ b/core/api/src/main/java/org/onosproject/event/AbstractListenerRegistry.java
@@ -33,7 +33,7 @@
private final Logger log = getLogger(getClass());
- private final Set<L> listeners = new CopyOnWriteArraySet<>();
+ protected final Set<L> listeners = new CopyOnWriteArraySet<>();
private volatile boolean shutdown = false;
/**
@@ -93,5 +93,4 @@
shutdown = true;
}
-
}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/AltTopoViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/AltTopoViewMessageHandler.java
index fc16133..935141d 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/AltTopoViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/AltTopoViewMessageHandler.java
@@ -41,6 +41,7 @@
import java.util.Map;
import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.System.currentTimeMillis;
import static org.onosproject.ui.impl.topo.TopoUiEvent.Type.SUMMARY_UPDATE;
/**
@@ -51,6 +52,7 @@
implements OverlayService {
private static final String TOPO_START = "topoStart";
+ private static final String TOPO_HEARTBEAT = "topoHeartbeat";
private static final String TOPO_STOP = "topoStop";
private static final String REQ_SUMMARY = "requestSummary";
private static final String CANCEL_SUMMARY = "cancelSummary";
@@ -60,7 +62,7 @@
protected ServiceDirectory directory;
protected TopoUiModelService modelService;
- private TopoUiListener modelListener;
+ private ModelListener modelListener;
private String version;
private SummaryGenerator defaultSummaryGenerator;
private SummaryGenerator currentSummaryGenerator;
@@ -83,22 +85,17 @@
@Override
public void destroy() {
-// cancelAllMonitoring();
-// stopListeningToModel();
+ cancelAllMonitoring();
+ stopListeningToModel();
super.destroy();
}
- private String getVersion() {
- String ver = directory.get(CoreService.class).version().toString();
- return ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
- }
-
-
@Override
protected Collection<RequestHandler> createRequestHandlers() {
return ImmutableSet.of(
new TopoStart(),
+ new TopoHeartbeat(),
new TopoStop(),
new ReqSummary(),
new CancelSummary()
@@ -107,6 +104,27 @@
}
// =====================================================================
+
+ private void cancelAllMonitoring() {
+ // TODO:
+ }
+
+ private void startListeningToModel() {
+ topoActive = true;
+ modelService.addListener(modelListener);
+ }
+
+ private void stopListeningToModel() {
+ topoActive = false;
+ modelService.removeListener(modelListener);
+ }
+
+ private String getVersion() {
+ String ver = directory.get(CoreService.class).version().toString();
+ return ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
+ }
+
+ // =====================================================================
// Overlay Service
// TODO: figure out how we are going to switch overlays in and out...
@@ -136,12 +154,21 @@
@Override
public void process(long sid, ObjectNode payload) {
- topoActive = true;
- modelService.addListener(modelListener);
+ startListeningToModel();
sendMessages(modelService.getInitialState());
}
}
+ private final class TopoHeartbeat extends RequestHandler {
+ private TopoHeartbeat() {
+ super(TOPO_HEARTBEAT);
+ }
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ modelListener.nudge();
+ }
+ }
+
private final class TopoStop extends RequestHandler {
private TopoStop() {
super(TOPO_STOP);
@@ -149,8 +176,7 @@
@Override
public void process(long sid, ObjectNode payload) {
- topoActive = false;
- modelService.removeListener(modelListener);
+ stopListeningToModel();
}
}
@@ -227,6 +253,10 @@
// Our listener for model events so we can push changes out to the UI...
private class ModelListener implements TopoUiListener {
+ private static final long AWAKE_THRESHOLD_MS = 6000;
+
+ private long lastNudged = currentTimeMillis();
+
@Override
public void event(TopoUiEvent event) {
log.debug("Handle Event: {}", event);
@@ -238,6 +268,15 @@
}
handler.handleEvent(event);
}
+
+ @Override
+ public boolean isAwake() {
+ return currentTimeMillis() - lastNudged < AWAKE_THRESHOLD_MS;
+ }
+
+ public void nudge() {
+ lastNudged = currentTimeMillis();
+ }
}
@@ -271,5 +310,4 @@
eventHandlerBinding.put(SUMMARY_UPDATE, summaryHandler);
// NOTE: no need to bind pass-thru handlers
}
-
}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
index 478bfe7..211058a 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
@@ -99,6 +99,7 @@
private static final String SPRITE_LIST_REQ = "spriteListRequest";
private static final String SPRITE_DATA_REQ = "spriteDataRequest";
private static final String TOPO_START = "topoStart";
+ private static final String TOPO_HEARTBEAT = "topoHeartbeat";
private static final String TOPO_STOP = "topoStop";
@@ -170,6 +171,7 @@
protected Collection<RequestHandler> createRequestHandlers() {
return ImmutableSet.of(
new TopoStart(),
+ new TopoHeartbeat(),
new TopoStop(),
new ReqSummary(),
new CancelSummary(),
@@ -211,6 +213,18 @@
}
@Deprecated
+ private final class TopoHeartbeat extends RequestHandler {
+ private TopoHeartbeat() {
+ super(TOPO_HEARTBEAT);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ // place holder for now
+ }
+ }
+
+ @Deprecated
private final class TopoStop extends RequestHandler {
private TopoStop() {
super(TOPO_STOP);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/ModelListenerRegistry.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/ModelListenerRegistry.java
new file mode 100644
index 0000000..04c6b72
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/ModelListenerRegistry.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.onosproject.ui.impl.topo;
+
+import org.onosproject.event.AbstractListenerRegistry;
+import org.slf4j.Logger;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * A listener registry that automatically prunes listeners that have not
+ * been sending heartbeat messages to assure us they are really listening.
+ */
+// package private
+class ModelListenerRegistry
+ extends AbstractListenerRegistry<TopoUiEvent, TopoUiListener> {
+
+ private final Logger log = getLogger(getClass());
+
+ private final Set<TopoUiListener> zombies = new HashSet<>();
+
+ @Override
+ public void process(TopoUiEvent event) {
+ zombies.clear();
+ for (TopoUiListener listener : listeners) {
+ try {
+ if (listener.isAwake()) {
+ listener.event(event);
+ } else {
+ zombies.add(listener);
+ }
+ } catch (Exception error) {
+ reportProblem(event, error);
+ }
+ }
+
+ // clean up zombie listeners
+ for (TopoUiListener z : zombies) {
+ log.debug("Removing zombie model listener: {}", z);
+ removeListener(z);
+ }
+ }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/OverlayService.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/OverlayService.java
index 08743ce..a046a12 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/OverlayService.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/OverlayService.java
@@ -22,6 +22,7 @@
/**
* Provides the API for external agents to inject topology overlay behavior.
*/
+// TODO: move to core-api module
public interface OverlayService {
/**
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/SummaryData.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/SummaryData.java
index 6dd93ee..c53d82d 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/SummaryData.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/SummaryData.java
@@ -20,6 +20,7 @@
/**
* Provides basic summary data for the topology.
*/
+// TODO: review -- move to core-api module?
public interface SummaryData {
/**
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUiListener.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUiListener.java
index df49954..8e8f2ed 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUiListener.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUiListener.java
@@ -24,4 +24,10 @@
*/
public interface TopoUiListener extends EventListener<TopoUiEvent> {
+ /**
+ * Returns true if the listener really is listening.
+ *
+ * @return true if awake
+ */
+ boolean isAwake();
}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUiModelManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUiModelManager.java
index 3a3bf68..c80c2e2 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUiModelManager.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoUiModelManager.java
@@ -27,7 +27,6 @@
import org.onosproject.cluster.ClusterEvent;
import org.onosproject.cluster.ClusterService;
import org.onosproject.cluster.ControllerNode;
-import org.onosproject.event.AbstractListenerRegistry;
import org.onosproject.event.EventDeliveryService;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.Device;
@@ -103,9 +102,8 @@
protected EventDeliveryService eventDispatcher;
- private AbstractListenerRegistry<TopoUiEvent, TopoUiListener>
- listenerRegistry = new AbstractListenerRegistry<>();
-
+ private final ModelListenerRegistry listenerRegistry =
+ new ModelListenerRegistry();
private final TopoMessageFactory messageFactory = new TopoMessageFactory();
private final MetaDb metaDb = new MetaDb();
@@ -138,17 +136,6 @@
}
- // TODO: figure out how to cull zombie listeners
- // The problem is when one refreshes the GUI (topology view)
- // a new instance of AltTopoViewMessageHandler is created and added
- // as a listener, but we never got a TopoStop event, which is what
- // causes the listener (for an AltTopoViewMessageHandler instance) to
- // be removed.
- // ==== Somehow need to tie this in to the GUI-disconnected event.
- // This probably requires client-generated heartbeat messages to
- // Keep the connection alive.
-
-
@Override
public void addListener(TopoUiListener listener) {
listenerRegistry.addListener(listener);
@@ -156,7 +143,12 @@
@Override
public void removeListener(TopoUiListener listener) {
- listenerRegistry.removeListener(listener);
+ // we don't really care if the listener is not listed...
+ try {
+ listenerRegistry.removeListener(listener);
+ } catch (IllegalArgumentException e) {
+ log.debug("Oops, listener not registered: {}", listener);
+ }
}
@Override
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 68f2e10..e6c943d 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoEvent.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoEvent.js
@@ -27,11 +27,14 @@
'use strict';
// injected refs
- var $log, wss, tps, tis, tfs, tss, tts, tspr;
+ var $log, $interval, wss, tps, tis, tfs, tss, tts, tspr;
// internal state
var handlerMap,
- openListener;
+ openListener,
+ heartbeatTimer;
+
+ var heartbeatPeriod = 5000; // 5 seconds
// ==========================
@@ -68,14 +71,31 @@
wss.sendEvent('topoStart');
}
+ function cancelHeartbeat() {
+ if (heartbeatTimer) {
+ $interval.cancel(heartbeatTimer);
+ }
+ heartbeatTimer = null;
+ }
+
+ function scheduleHeartbeat() {
+ cancelHeartbeat();
+ heartbeatTimer = $interval(function () {
+ wss.sendEvent('topoHeartbeat');
+ }, heartbeatPeriod);
+ }
+
+
angular.module('ovTopo')
.factory('TopoEventService',
- ['$log', '$location', 'WebSocketService',
+ ['$log', '$interval', 'WebSocketService',
'TopoPanelService', 'TopoInstService', 'TopoForceService',
'TopoSelectService', 'TopoTrafficService', 'TopoSpriteService',
- function (_$log_, $loc, _wss_, _tps_, _tis_, _tfs_, _tss_, _tts_, _tspr_) {
+ function (_$log_, _$interval_, _wss_,
+ _tps_, _tis_, _tfs_, _tss_, _tts_, _tspr_) {
$log = _$log_;
+ $interval = _$interval_;
wss = _wss_;
tps = _tps_;
tis = _tis_;
@@ -90,10 +110,12 @@
openListener = wss.addOpenListener(wsOpen);
wss.bindHandlers(handlerMap);
wss.sendEvent('topoStart');
+ scheduleHeartbeat();
$log.debug('topo comms started');
}
function stop() {
+ cancelHeartbeat();
wss.sendEvent('topoStop');
wss.unbindHandlers(handlerMap);
wss.removeOpenListener(openListener);