diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
commit | 13d05bc8458758ee39cb829098241e89616717ee (patch) | |
tree | 22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java | |
parent | 6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff) |
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java')
-rw-r--r-- | framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java new file mode 100644 index 00000000..1b6f7f99 --- /dev/null +++ b/framework/src/onos/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java @@ -0,0 +1,240 @@ +/* + * 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.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.eclipse.jetty.websocket.WebSocket; +import org.onlab.osgi.ServiceDirectory; +import org.onlab.osgi.ServiceNotFoundException; +import org.onosproject.cluster.ClusterService; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.ui.UiConnection; +import org.onosproject.ui.UiExtensionService; +import org.onosproject.ui.UiMessageHandlerFactory; +import org.onosproject.ui.UiMessageHandler; +import org.onosproject.ui.UiTopoOverlayFactory; +import org.onosproject.ui.topo.TopoConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Web socket capable of interacting with the GUI. + */ +public class UiWebSocket + implements UiConnection, WebSocket.OnTextMessage, WebSocket.OnControl { + + private static final Logger log = LoggerFactory.getLogger(UiWebSocket.class); + + private static final long MAX_AGE_MS = 30_000; + + private static final byte PING = 0x9; + private static final byte PONG = 0xA; + private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad}; + + private final ServiceDirectory directory; + + private Connection connection; + private FrameConnection control; + + private final ObjectMapper mapper = new ObjectMapper(); + + private long lastActive = System.currentTimeMillis(); + + private Map<String, UiMessageHandler> handlers; + private TopoOverlayCache overlayCache; + + /** + * Creates a new web-socket for serving data to GUI. + * + * @param directory service directory + */ + public UiWebSocket(ServiceDirectory directory) { + this.directory = directory; + } + + /** + * Issues a close on the connection. + */ + synchronized void close() { + destroyHandlersAndOverlays(); + if (connection.isOpen()) { + connection.close(); + } + } + + /** + * Indicates if this connection is idle. + * + * @return true if idle or closed + */ + synchronized boolean isIdle() { + long quietFor = System.currentTimeMillis() - lastActive; + boolean idle = quietFor > MAX_AGE_MS; + if (idle || (connection != null && !connection.isOpen())) { + log.debug("IDLE (or closed) websocket [{} ms]", quietFor); + return true; + } else if (connection != null) { + try { + control.sendControl(PING, PING_DATA, 0, PING_DATA.length); + } catch (IOException e) { + log.warn("Unable to send ping message due to: ", e); + } + } + return false; + } + + @Override + public void onOpen(Connection connection) { + this.connection = connection; + this.control = (FrameConnection) connection; + try { + createHandlersAndOverlays(); + sendInstanceData(); + log.info("GUI client connected"); + + } catch (ServiceNotFoundException e) { + log.warn("Unable to open GUI connection; services have been shut-down", e); + this.connection.close(); + this.connection = null; + this.control = null; + } + } + + @Override + public synchronized void onClose(int closeCode, String message) { + destroyHandlersAndOverlays(); + log.info("GUI client disconnected [close-code={}, message={}]", + closeCode, message); + } + + @Override + public boolean onControl(byte controlCode, byte[] data, int offset, int length) { + lastActive = System.currentTimeMillis(); + return true; + } + + @Override + public void onMessage(String data) { + log.debug("onMessage: {}", data); + lastActive = System.currentTimeMillis(); + try { + ObjectNode message = (ObjectNode) mapper.reader().readTree(data); + String type = message.path("event").asText("unknown"); + UiMessageHandler handler = handlers.get(type); + if (handler != null) { + handler.process(message); + } else { + log.warn("No GUI message handler for type {}", type); + } + } catch (Exception e) { + log.warn("Unable to parse GUI message {} due to {}", data, e); + log.debug("Boom!!!", e); + } + } + + @Override + public synchronized void sendMessage(ObjectNode message) { + try { + if (connection.isOpen()) { + connection.sendMessage(message.toString()); + } + } catch (IOException e) { + log.warn("Unable to send message {} to GUI due to {}", message, e); + log.debug("Boom!!!", e); + } + } + + @Override + public synchronized void sendMessage(String type, long sid, ObjectNode payload) { + ObjectNode message = mapper.createObjectNode(); + message.put("event", type); + if (sid > 0) { + message.put("sid", sid); + } + message.set("payload", payload); + sendMessage(message); + + } + + // Creates new message handlers. + private synchronized void createHandlersAndOverlays() { + log.debug("creating handlers and overlays..."); + handlers = new HashMap<>(); + overlayCache = new TopoOverlayCache(); + + UiExtensionService service = directory.get(UiExtensionService.class); + 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)); + + // need to inject the overlay cache into topology message handler + if (handler instanceof TopologyViewMessageHandler) { + ((TopologyViewMessageHandler) handler).setOverlayCache(overlayCache); + } + }); + } + + UiTopoOverlayFactory overlayFactory = ext.topoOverlayFactory(); + if (overlayFactory != null) { + overlayFactory.newOverlays().forEach(overlayCache::add); + } + }); + log.debug("#handlers = {}, #overlays = {}", handlers.size(), + overlayCache.size()); + } + + // Destroys message handlers. + private synchronized void destroyHandlersAndOverlays() { + log.debug("destroying handlers and overlays..."); + handlers.forEach((type, handler) -> handler.destroy()); + handlers.clear(); + + if (overlayCache != null) { + overlayCache.destroy(); + overlayCache = null; + } + } + + // Sends cluster node/instance information to allow GUI to fail-over. + private void sendInstanceData() { + ClusterService service = directory.get(ClusterService.class); + ArrayNode instances = mapper.createArrayNode(); + + for (ControllerNode node : service.getNodes()) { + ObjectNode instance = mapper.createObjectNode() + .put("id", node.id().toString()) + .put("ip", node.ip().toString()) + .put(TopoConstants.Glyphs.UI_ATTACHED, + node.equals(service.getLocalNode())); + instances.add(instance); + } + + ObjectNode payload = mapper.createObjectNode(); + payload.set("clusterNodes", instances); + sendMessage("bootstrap", 0, payload); + } + +} + |