blob: 31c3f84079fe5bce98303176241d111d6dd43efc [file] [log] [blame]
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001/*
2 * Copyright 2014 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onlab.onos.gui;
17
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080018import com.fasterxml.jackson.databind.JsonNode;
19import com.fasterxml.jackson.databind.ObjectMapper;
20import com.fasterxml.jackson.databind.node.ArrayNode;
21import com.fasterxml.jackson.databind.node.ObjectNode;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080022import org.eclipse.jetty.websocket.WebSocket;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080023import org.onlab.onos.event.Event;
24import org.onlab.onos.net.Annotations;
25import org.onlab.onos.net.Device;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080026import org.onlab.onos.net.DeviceId;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080027import org.onlab.onos.net.Link;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080028import org.onlab.onos.net.Path;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080029import org.onlab.onos.net.device.DeviceEvent;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080030import org.onlab.onos.net.device.DeviceService;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080031import org.onlab.onos.net.link.LinkEvent;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080032import org.onlab.onos.net.topology.Topology;
33import org.onlab.onos.net.topology.TopologyEdge;
34import org.onlab.onos.net.topology.TopologyEvent;
35import org.onlab.onos.net.topology.TopologyGraph;
36import org.onlab.onos.net.topology.TopologyListener;
37import org.onlab.onos.net.topology.TopologyService;
38import org.onlab.onos.net.topology.TopologyVertex;
39import org.onlab.osgi.ServiceDirectory;
40
41import java.io.IOException;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080042import java.util.HashMap;
43import java.util.Map;
44import java.util.Set;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080045
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080046import static org.onlab.onos.net.DeviceId.deviceId;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080047import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
48import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
49import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
50import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
51
Thomas Vachuska7d638d32014-11-07 10:24:43 -080052/**
53 * Web socket capable of interacting with the GUI topology view.
54 */
55public class TopologyWebSocket implements WebSocket.OnTextMessage, TopologyListener {
56
57 private final ServiceDirectory directory;
58 private final TopologyService topologyService;
59 private final DeviceService deviceService;
60
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080061 private final ObjectMapper mapper = new ObjectMapper();
62
Thomas Vachuska7d638d32014-11-07 10:24:43 -080063 private Connection connection;
64
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080065 // TODO: extract into an external & durable state; good enough for now and demo
66 private static Map<String, ObjectNode> metaUi = new HashMap<>();
67
68 private static final String COMPACT = "%s/%s-%s/%s";
69
70
Thomas Vachuska7d638d32014-11-07 10:24:43 -080071 /**
72 * Creates a new web-socket for serving data to GUI topology view.
73 *
74 * @param directory service directory
75 */
76 public TopologyWebSocket(ServiceDirectory directory) {
77 this.directory = directory;
78 topologyService = directory.get(TopologyService.class);
79 deviceService = directory.get(DeviceService.class);
80 }
81
82 @Override
83 public void onOpen(Connection connection) {
84 this.connection = connection;
85
86 // Register for topology events...
87 if (topologyService != null && deviceService != null) {
88 topologyService.addListener(this);
89
Thomas Vachuska7d638d32014-11-07 10:24:43 -080090 Topology topology = topologyService.currentTopology();
91 TopologyGraph graph = topologyService.getGraph(topology);
92 for (TopologyVertex vertex : graph.getVertexes()) {
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080093 sendMessage(message(new DeviceEvent(DEVICE_ADDED,
94 deviceService.getDevice(vertex.deviceId()))));
Thomas Vachuska7d638d32014-11-07 10:24:43 -080095 }
96
97 for (TopologyEdge edge : graph.getEdges()) {
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080098 sendMessage(message(new LinkEvent(LINK_ADDED, edge.link())));
Thomas Vachuska7d638d32014-11-07 10:24:43 -080099 }
100
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800101 } else {
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800102 sendMessage(message("error", "No topology service!!!"));
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800103 }
104 }
105
106 @Override
107 public void onClose(int closeCode, String message) {
108 TopologyService topologyService = directory.get(TopologyService.class);
109 if (topologyService != null) {
110 topologyService.removeListener(this);
111 }
112 }
113
114 @Override
115 public void onMessage(String data) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800116 try {
117 ObjectNode event = (ObjectNode) mapper.reader().readTree(data);
118 String type = event.path("event").asText("unknown");
119 ObjectNode payload = (ObjectNode) event.path("payload");
120
121 switch (type) {
122 case "updateMeta":
123 metaUi.put(payload.path("id").asText(), payload);
124 break;
125 case "requestPath":
126 findPath(deviceId(payload.path("one").asText()),
127 deviceId(payload.path("two").asText()));
128 default:
129 break;
130 }
131 } catch (IOException e) {
132 System.out.println("Received: " + data);
133 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800134 }
135
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800136 private void findPath(DeviceId one, DeviceId two) {
137 Set<Path> paths = topologyService.getPaths(topologyService.currentTopology(),
138 one, two);
139 if (!paths.isEmpty()) {
140 ObjectNode payload = mapper.createObjectNode();
141 ArrayNode links = mapper.createArrayNode();
142
143 Path path = paths.iterator().next();
144 for (Link link : path.links()) {
145 links.add(compactLinkString(link));
146 }
147
148 payload.set("links", links);
149 sendMessage(envelope("showPath", payload));
150 }
151 // TODO: when no path, send a message to the client
152 }
153
154 /**
155 * Returns a compact string representing the given link.
156 *
157 * @param link infrastructure link
158 * @return formatted link string
159 */
160 public static String compactLinkString(Link link) {
161 return String.format(COMPACT, link.src().deviceId(), link.src().port(),
162 link.dst().deviceId(), link.dst().port());
163 }
164
165
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800166 private void sendMessage(String data) {
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800167 try {
168 connection.sendMessage(data);
169 } catch (IOException e) {
170 e.printStackTrace();
171 }
172 }
173
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800174 // Produces a link event message to the client.
175 private String message(DeviceEvent event) {
176 Device device = event.subject();
177 ObjectNode payload = mapper.createObjectNode()
178 .put("id", device.id().toString())
179 .put("type", device.type().toString().toLowerCase())
180 .put("online", deviceService.isAvailable(device.id()));
181
182 // Generate labels: id, chassis id, no-label, optional-name
183 ArrayNode labels = mapper.createArrayNode();
184 labels.add(device.id().toString());
185 labels.add(device.chassisId().toString());
186 labels.add(" "); // compact no-label view
187 labels.add(device.annotations().value("name"));
188
189 // Add labels, props and stuff the payload into envelope.
190 payload.set("labels", labels);
191 payload.set("props", props(device.annotations()));
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800192
193 ObjectNode meta = metaUi.get(device.id().toString());
194 if (meta != null) {
195 payload.set("metaUi", meta);
196 }
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800197
198 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
199 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
200 return envelope(type, payload);
201 }
202
203 // Produces a link event message to the client.
204 private String message(LinkEvent event) {
205 Link link = event.subject();
206 ObjectNode payload = mapper.createObjectNode()
207 .put("type", link.type().toString().toLowerCase())
208 .put("linkWidth", 2)
209 .put("src", link.src().deviceId().toString())
210 .put("srcPort", link.src().port().toString())
211 .put("dst", link.dst().deviceId().toString())
212 .put("dstPort", link.dst().port().toString());
213 String type = (event.type() == LINK_ADDED) ? "addLink" :
214 ((event.type() == LINK_REMOVED) ? "removeLink" : "removeLink");
215 return envelope(type, payload);
216 }
217
218 // Produces JSON structure from annotations.
219 private JsonNode props(Annotations annotations) {
220 ObjectNode props = mapper.createObjectNode();
221 for (String key : annotations.keys()) {
222 props.put(key, annotations.value(key));
223 }
224 return props;
225 }
226
227 // Produces a log message event bound to the client.
228 private String message(String severity, String message) {
229 return envelope("message",
230 mapper.createObjectNode()
231 .put("severity", severity)
232 .put("message", message));
233 }
234
235 // Puts the payload into an envelope and returns it.
236 private String envelope(String type, ObjectNode payload) {
237 ObjectNode event = mapper.createObjectNode();
238 event.put("event", type);
239 event.set("payload", payload);
240 return event.toString();
241 }
242
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800243 @Override
244 public void event(TopologyEvent event) {
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800245 for (Event reason : event.reasons()) {
246 if (reason instanceof DeviceEvent) {
247 sendMessage(message((DeviceEvent) reason));
248 } else if (reason instanceof LinkEvent) {
249 sendMessage(message((LinkEvent) reason));
250 }
251 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800252 }
253}
254