Deprecating old web-socket stuff and adding ability for client-side message hand…
…ler registration. Failover still to be done and same for the async hooks. Change-Id: I6029c91eb1a04e01401e495b9673ddaea728e215
Showing
14 changed files
with
1755 additions
and
144 deletions
... | @@ -31,6 +31,7 @@ import java.util.TimerTask; | ... | @@ -31,6 +31,7 @@ import java.util.TimerTask; |
31 | /** | 31 | /** |
32 | * Web socket servlet capable of creating various sockets for the user interface. | 32 | * Web socket servlet capable of creating various sockets for the user interface. |
33 | */ | 33 | */ |
34 | +@Deprecated | ||
34 | public class GuiWebSocketServlet extends WebSocketServlet { | 35 | public class GuiWebSocketServlet extends WebSocketServlet { |
35 | 36 | ||
36 | private static final long PING_DELAY_MS = 5000; | 37 | private static final long PING_DELAY_MS = 5000; | ... | ... |
1 | +/* | ||
2 | + * Copyright 2015 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 | + */ | ||
16 | +package org.onosproject.ui.impl; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import com.fasterxml.jackson.databind.node.ArrayNode; | ||
20 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
21 | +import com.google.common.collect.ImmutableSet; | ||
22 | +import org.onlab.osgi.ServiceDirectory; | ||
23 | +import org.onlab.util.AbstractAccumulator; | ||
24 | +import org.onlab.util.Accumulator; | ||
25 | +import org.onosproject.cluster.ClusterEvent; | ||
26 | +import org.onosproject.cluster.ClusterEventListener; | ||
27 | +import org.onosproject.cluster.ControllerNode; | ||
28 | +import org.onosproject.core.ApplicationId; | ||
29 | +import org.onosproject.core.CoreService; | ||
30 | +import org.onosproject.event.Event; | ||
31 | +import org.onosproject.mastership.MastershipAdminService; | ||
32 | +import org.onosproject.mastership.MastershipEvent; | ||
33 | +import org.onosproject.mastership.MastershipListener; | ||
34 | +import org.onosproject.net.ConnectPoint; | ||
35 | +import org.onosproject.net.Device; | ||
36 | +import org.onosproject.net.Host; | ||
37 | +import org.onosproject.net.HostId; | ||
38 | +import org.onosproject.net.HostLocation; | ||
39 | +import org.onosproject.net.Link; | ||
40 | +import org.onosproject.net.device.DeviceEvent; | ||
41 | +import org.onosproject.net.device.DeviceListener; | ||
42 | +import org.onosproject.net.flow.DefaultTrafficSelector; | ||
43 | +import org.onosproject.net.flow.DefaultTrafficTreatment; | ||
44 | +import org.onosproject.net.flow.FlowRuleEvent; | ||
45 | +import org.onosproject.net.flow.FlowRuleListener; | ||
46 | +import org.onosproject.net.flow.TrafficSelector; | ||
47 | +import org.onosproject.net.flow.TrafficTreatment; | ||
48 | +import org.onosproject.net.host.HostEvent; | ||
49 | +import org.onosproject.net.host.HostListener; | ||
50 | +import org.onosproject.net.intent.HostToHostIntent; | ||
51 | +import org.onosproject.net.intent.Intent; | ||
52 | +import org.onosproject.net.intent.IntentEvent; | ||
53 | +import org.onosproject.net.intent.IntentListener; | ||
54 | +import org.onosproject.net.intent.MultiPointToSinglePointIntent; | ||
55 | +import org.onosproject.net.link.LinkEvent; | ||
56 | +import org.onosproject.net.link.LinkListener; | ||
57 | +import org.onosproject.ui.UiConnection; | ||
58 | + | ||
59 | +import java.util.ArrayList; | ||
60 | +import java.util.Collections; | ||
61 | +import java.util.Comparator; | ||
62 | +import java.util.HashSet; | ||
63 | +import java.util.List; | ||
64 | +import java.util.Set; | ||
65 | +import java.util.Timer; | ||
66 | +import java.util.TimerTask; | ||
67 | + | ||
68 | +import static com.google.common.base.Strings.isNullOrEmpty; | ||
69 | +import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED; | ||
70 | +import static org.onosproject.net.DeviceId.deviceId; | ||
71 | +import static org.onosproject.net.HostId.hostId; | ||
72 | +import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED; | ||
73 | +import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_UPDATED; | ||
74 | +import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED; | ||
75 | +import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED; | ||
76 | + | ||
77 | +/** | ||
78 | + * Web socket capable of interacting with the GUI topology view. | ||
79 | + */ | ||
80 | +public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase { | ||
81 | + | ||
82 | + private static final String APP_ID = "org.onosproject.gui"; | ||
83 | + | ||
84 | + private static final long TRAFFIC_FREQUENCY = 5000; | ||
85 | + private static final long SUMMARY_FREQUENCY = 30000; | ||
86 | + | ||
87 | + private static final Comparator<? super ControllerNode> NODE_COMPARATOR = | ||
88 | + new Comparator<ControllerNode>() { | ||
89 | + @Override | ||
90 | + public int compare(ControllerNode o1, ControllerNode o2) { | ||
91 | + return o1.id().toString().compareTo(o2.id().toString()); | ||
92 | + } | ||
93 | + }; | ||
94 | + | ||
95 | + | ||
96 | + private final Timer timer = new Timer("topology-view"); | ||
97 | + | ||
98 | + private static final int MAX_EVENTS = 1000; | ||
99 | + private static final int MAX_BATCH_MS = 5000; | ||
100 | + private static final int MAX_IDLE_MS = 1000; | ||
101 | + | ||
102 | + private ApplicationId appId; | ||
103 | + | ||
104 | + private final ClusterEventListener clusterListener = new InternalClusterListener(); | ||
105 | + private final MastershipListener mastershipListener = new InternalMastershipListener(); | ||
106 | + private final DeviceListener deviceListener = new InternalDeviceListener(); | ||
107 | + private final LinkListener linkListener = new InternalLinkListener(); | ||
108 | + private final HostListener hostListener = new InternalHostListener(); | ||
109 | + private final IntentListener intentListener = new InternalIntentListener(); | ||
110 | + private final FlowRuleListener flowListener = new InternalFlowListener(); | ||
111 | + | ||
112 | + private final Accumulator<Event> eventAccummulator = new InternalEventAccummulator(); | ||
113 | + | ||
114 | + private TimerTask trafficTask; | ||
115 | + private ObjectNode trafficEvent; | ||
116 | + | ||
117 | + private TimerTask summaryTask; | ||
118 | + private ObjectNode summaryEvent; | ||
119 | + | ||
120 | + private boolean listenersRemoved = false; | ||
121 | + | ||
122 | + private TopologyViewIntentFilter intentFilter; | ||
123 | + | ||
124 | + // Current selection context | ||
125 | + private Set<Host> selectedHosts; | ||
126 | + private Set<Device> selectedDevices; | ||
127 | + private List<Intent> selectedIntents; | ||
128 | + private int currentIntentIndex = -1; | ||
129 | + | ||
130 | + /** | ||
131 | + * Creates a new web-socket for serving data to GUI topology view. | ||
132 | + */ | ||
133 | + public TopologyViewMessageHandler() { | ||
134 | + super(ImmutableSet.of("topoStart", "topoStop", | ||
135 | + "requestDetails", | ||
136 | + "updateMeta", | ||
137 | + "addHostIntent", | ||
138 | + "addMultiSourceIntent", | ||
139 | + "requestRelatedIntents", | ||
140 | + "requestNextRelatedIntent", | ||
141 | + "requestPrevRelatedIntent", | ||
142 | + "requestSelectedIntentTraffic", | ||
143 | + "requestAllTraffic", | ||
144 | + "requestDeviceLinkFlows", | ||
145 | + "cancelTraffic", | ||
146 | + "requestSummary", | ||
147 | + "cancelSummary", | ||
148 | + "equalizeMasters" | ||
149 | + )); | ||
150 | + } | ||
151 | + | ||
152 | + @Override | ||
153 | + public void init(UiConnection connection, ServiceDirectory directory) { | ||
154 | + super.init(connection, directory); | ||
155 | + intentFilter = new TopologyViewIntentFilter(intentService, deviceService, | ||
156 | + hostService, linkService); | ||
157 | + appId = directory.get(CoreService.class).registerApplication(APP_ID); | ||
158 | + } | ||
159 | + | ||
160 | + @Override | ||
161 | + public void destroy() { | ||
162 | + cancelAllRequests(); | ||
163 | + super.destroy(); | ||
164 | + } | ||
165 | + | ||
166 | + // Processes the specified event. | ||
167 | + @Override | ||
168 | + public void process(ObjectNode event) { | ||
169 | + String type = string(event, "event", "unknown"); | ||
170 | + if (type.equals("requestDetails")) { | ||
171 | + requestDetails(event); | ||
172 | + } else if (type.equals("updateMeta")) { | ||
173 | + updateMetaUi(event); | ||
174 | + | ||
175 | + } else if (type.equals("addHostIntent")) { | ||
176 | + createHostIntent(event); | ||
177 | + } else if (type.equals("addMultiSourceIntent")) { | ||
178 | + createMultiSourceIntent(event); | ||
179 | + | ||
180 | + } else if (type.equals("requestRelatedIntents")) { | ||
181 | + stopTrafficMonitoring(); | ||
182 | + requestRelatedIntents(event); | ||
183 | + | ||
184 | + } else if (type.equals("requestNextRelatedIntent")) { | ||
185 | + stopTrafficMonitoring(); | ||
186 | + requestAnotherRelatedIntent(event, +1); | ||
187 | + } else if (type.equals("requestPrevRelatedIntent")) { | ||
188 | + stopTrafficMonitoring(); | ||
189 | + requestAnotherRelatedIntent(event, -1); | ||
190 | + } else if (type.equals("requestSelectedIntentTraffic")) { | ||
191 | + requestSelectedIntentTraffic(event); | ||
192 | + startTrafficMonitoring(event); | ||
193 | + | ||
194 | + } else if (type.equals("requestAllTraffic")) { | ||
195 | + requestAllTraffic(event); | ||
196 | + startTrafficMonitoring(event); | ||
197 | + | ||
198 | + } else if (type.equals("requestDeviceLinkFlows")) { | ||
199 | + requestDeviceLinkFlows(event); | ||
200 | + startTrafficMonitoring(event); | ||
201 | + | ||
202 | + } else if (type.equals("cancelTraffic")) { | ||
203 | + cancelTraffic(event); | ||
204 | + | ||
205 | + } else if (type.equals("requestSummary")) { | ||
206 | + requestSummary(event); | ||
207 | + startSummaryMonitoring(event); | ||
208 | + } else if (type.equals("cancelSummary")) { | ||
209 | + stopSummaryMonitoring(); | ||
210 | + | ||
211 | + } else if (type.equals("equalizeMasters")) { | ||
212 | + equalizeMasters(event); | ||
213 | + | ||
214 | + } else if (type.equals("topoStart")) { | ||
215 | + sendAllInitialData(); | ||
216 | + } else if (type.equals("topoStop")) { | ||
217 | + cancelAllRequests(); | ||
218 | + } | ||
219 | + } | ||
220 | + | ||
221 | + // Sends the specified data to the client. | ||
222 | + protected synchronized void sendMessage(ObjectNode data) { | ||
223 | + UiConnection connection = connection(); | ||
224 | + if (connection != null) { | ||
225 | + connection.sendMessage(data); | ||
226 | + } | ||
227 | + } | ||
228 | + | ||
229 | + private void sendAllInitialData() { | ||
230 | + addListeners(); | ||
231 | + sendAllInstances(null); | ||
232 | + sendAllDevices(); | ||
233 | + sendAllLinks(); | ||
234 | + sendAllHosts(); | ||
235 | + | ||
236 | + } | ||
237 | + | ||
238 | + private void cancelAllRequests() { | ||
239 | + stopSummaryMonitoring(); | ||
240 | + stopTrafficMonitoring(); | ||
241 | + removeListeners(); | ||
242 | + } | ||
243 | + | ||
244 | + // Sends all controller nodes to the client as node-added messages. | ||
245 | + private void sendAllInstances(String messageType) { | ||
246 | + List<ControllerNode> nodes = new ArrayList<>(clusterService.getNodes()); | ||
247 | + Collections.sort(nodes, NODE_COMPARATOR); | ||
248 | + for (ControllerNode node : nodes) { | ||
249 | + sendMessage(instanceMessage(new ClusterEvent(INSTANCE_ADDED, node), | ||
250 | + messageType)); | ||
251 | + } | ||
252 | + } | ||
253 | + | ||
254 | + // Sends all devices to the client as device-added messages. | ||
255 | + private void sendAllDevices() { | ||
256 | + // Send optical first, others later for layered rendering | ||
257 | + for (Device device : deviceService.getDevices()) { | ||
258 | + if (device.type() == Device.Type.ROADM) { | ||
259 | + sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device))); | ||
260 | + } | ||
261 | + } | ||
262 | + for (Device device : deviceService.getDevices()) { | ||
263 | + if (device.type() != Device.Type.ROADM) { | ||
264 | + sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device))); | ||
265 | + } | ||
266 | + } | ||
267 | + } | ||
268 | + | ||
269 | + // Sends all links to the client as link-added messages. | ||
270 | + private void sendAllLinks() { | ||
271 | + // Send optical first, others later for layered rendering | ||
272 | + for (Link link : linkService.getLinks()) { | ||
273 | + if (link.type() == Link.Type.OPTICAL) { | ||
274 | + sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link))); | ||
275 | + } | ||
276 | + } | ||
277 | + for (Link link : linkService.getLinks()) { | ||
278 | + if (link.type() != Link.Type.OPTICAL) { | ||
279 | + sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link))); | ||
280 | + } | ||
281 | + } | ||
282 | + } | ||
283 | + | ||
284 | + // Sends all hosts to the client as host-added messages. | ||
285 | + private void sendAllHosts() { | ||
286 | + for (Host host : hostService.getHosts()) { | ||
287 | + sendMessage(hostMessage(new HostEvent(HOST_ADDED, host))); | ||
288 | + } | ||
289 | + } | ||
290 | + | ||
291 | + // Sends back device or host details. | ||
292 | + private void requestDetails(ObjectNode event) { | ||
293 | + ObjectNode payload = payload(event); | ||
294 | + String type = string(payload, "class", "unknown"); | ||
295 | + long sid = number(event, "sid"); | ||
296 | + | ||
297 | + if (type.equals("device")) { | ||
298 | + sendMessage(deviceDetails(deviceId(string(payload, "id")), sid)); | ||
299 | + } else if (type.equals("host")) { | ||
300 | + sendMessage(hostDetails(hostId(string(payload, "id")), sid)); | ||
301 | + } | ||
302 | + } | ||
303 | + | ||
304 | + | ||
305 | + // Creates host-to-host intent. | ||
306 | + private void createHostIntent(ObjectNode event) { | ||
307 | + ObjectNode payload = payload(event); | ||
308 | + long id = number(event, "sid"); | ||
309 | + // TODO: add protection against device ids and non-existent hosts. | ||
310 | + HostId one = hostId(string(payload, "one")); | ||
311 | + HostId two = hostId(string(payload, "two")); | ||
312 | + | ||
313 | + HostToHostIntent intent = | ||
314 | + new HostToHostIntent(appId, one, two, | ||
315 | + DefaultTrafficSelector.builder().build(), | ||
316 | + DefaultTrafficTreatment.builder().build()); | ||
317 | + | ||
318 | + intentService.submit(intent); | ||
319 | + startMonitoringIntent(event, intent); | ||
320 | + } | ||
321 | + | ||
322 | + // Creates multi-source-to-single-dest intent. | ||
323 | + private void createMultiSourceIntent(ObjectNode event) { | ||
324 | + ObjectNode payload = payload(event); | ||
325 | + long id = number(event, "sid"); | ||
326 | + // TODO: add protection against device ids and non-existent hosts. | ||
327 | + Set<HostId> src = getHostIds((ArrayNode) payload.path("src")); | ||
328 | + HostId dst = hostId(string(payload, "dst")); | ||
329 | + Host dstHost = hostService.getHost(dst); | ||
330 | + | ||
331 | + Set<ConnectPoint> ingressPoints = getHostLocations(src); | ||
332 | + | ||
333 | + // FIXME: clearly, this is not enough | ||
334 | + TrafficSelector selector = DefaultTrafficSelector.builder() | ||
335 | + .matchEthDst(dstHost.mac()).build(); | ||
336 | + TrafficTreatment treatment = DefaultTrafficTreatment.builder().build(); | ||
337 | + | ||
338 | + MultiPointToSinglePointIntent intent = | ||
339 | + new MultiPointToSinglePointIntent(appId, selector, treatment, | ||
340 | + ingressPoints, dstHost.location()); | ||
341 | + | ||
342 | + intentService.submit(intent); | ||
343 | + startMonitoringIntent(event, intent); | ||
344 | + } | ||
345 | + | ||
346 | + | ||
347 | + private synchronized void startMonitoringIntent(ObjectNode event, Intent intent) { | ||
348 | + selectedHosts = new HashSet<>(); | ||
349 | + selectedDevices = new HashSet<>(); | ||
350 | + selectedIntents = new ArrayList<>(); | ||
351 | + selectedIntents.add(intent); | ||
352 | + currentIntentIndex = -1; | ||
353 | + requestAnotherRelatedIntent(event, +1); | ||
354 | + requestSelectedIntentTraffic(event); | ||
355 | + } | ||
356 | + | ||
357 | + | ||
358 | + private Set<ConnectPoint> getHostLocations(Set<HostId> hostIds) { | ||
359 | + Set<ConnectPoint> points = new HashSet<>(); | ||
360 | + for (HostId hostId : hostIds) { | ||
361 | + points.add(getHostLocation(hostId)); | ||
362 | + } | ||
363 | + return points; | ||
364 | + } | ||
365 | + | ||
366 | + private HostLocation getHostLocation(HostId hostId) { | ||
367 | + return hostService.getHost(hostId).location(); | ||
368 | + } | ||
369 | + | ||
370 | + // Produces a list of host ids from the specified JSON array. | ||
371 | + private Set<HostId> getHostIds(ArrayNode ids) { | ||
372 | + Set<HostId> hostIds = new HashSet<>(); | ||
373 | + for (JsonNode id : ids) { | ||
374 | + hostIds.add(hostId(id.asText())); | ||
375 | + } | ||
376 | + return hostIds; | ||
377 | + } | ||
378 | + | ||
379 | + | ||
380 | + private synchronized long startTrafficMonitoring(ObjectNode event) { | ||
381 | + stopTrafficMonitoring(); | ||
382 | + trafficEvent = event; | ||
383 | + trafficTask = new TrafficMonitor(); | ||
384 | + timer.schedule(trafficTask, TRAFFIC_FREQUENCY, TRAFFIC_FREQUENCY); | ||
385 | + return number(event, "sid"); | ||
386 | + } | ||
387 | + | ||
388 | + private synchronized void stopTrafficMonitoring() { | ||
389 | + if (trafficTask != null) { | ||
390 | + trafficTask.cancel(); | ||
391 | + trafficTask = null; | ||
392 | + trafficEvent = null; | ||
393 | + } | ||
394 | + } | ||
395 | + | ||
396 | + // Subscribes for host traffic messages. | ||
397 | + private synchronized void requestAllTraffic(ObjectNode event) { | ||
398 | + long sid = startTrafficMonitoring(event); | ||
399 | + sendMessage(trafficSummaryMessage(sid)); | ||
400 | + } | ||
401 | + | ||
402 | + private void requestDeviceLinkFlows(ObjectNode event) { | ||
403 | + ObjectNode payload = payload(event); | ||
404 | + long sid = startTrafficMonitoring(event); | ||
405 | + | ||
406 | + // Get the set of selected hosts and their intents. | ||
407 | + ArrayNode ids = (ArrayNode) payload.path("ids"); | ||
408 | + Set<Host> hosts = new HashSet<>(); | ||
409 | + Set<Device> devices = getDevices(ids); | ||
410 | + | ||
411 | + // If there is a hover node, include it in the hosts and find intents. | ||
412 | + String hover = string(payload, "hover"); | ||
413 | + if (!isNullOrEmpty(hover)) { | ||
414 | + addHover(hosts, devices, hover); | ||
415 | + } | ||
416 | + sendMessage(flowSummaryMessage(sid, devices)); | ||
417 | + } | ||
418 | + | ||
419 | + | ||
420 | + // Requests related intents message. | ||
421 | + private synchronized void requestRelatedIntents(ObjectNode event) { | ||
422 | + ObjectNode payload = payload(event); | ||
423 | + if (!payload.has("ids")) { | ||
424 | + return; | ||
425 | + } | ||
426 | + | ||
427 | + long sid = number(event, "sid"); | ||
428 | + | ||
429 | + // Cancel any other traffic monitoring mode. | ||
430 | + stopTrafficMonitoring(); | ||
431 | + | ||
432 | + // Get the set of selected hosts and their intents. | ||
433 | + ArrayNode ids = (ArrayNode) payload.path("ids"); | ||
434 | + selectedHosts = getHosts(ids); | ||
435 | + selectedDevices = getDevices(ids); | ||
436 | + selectedIntents = intentFilter.findPathIntents(selectedHosts, selectedDevices, | ||
437 | + intentService.getIntents()); | ||
438 | + currentIntentIndex = -1; | ||
439 | + | ||
440 | + if (haveSelectedIntents()) { | ||
441 | + // Send a message to highlight all links of all monitored intents. | ||
442 | + sendMessage(trafficMessage(sid, new TrafficClass("primary", selectedIntents))); | ||
443 | + } | ||
444 | + | ||
445 | + // FIXME: Re-introduce one the client click vs hover gesture stuff is sorted out. | ||
446 | +// String hover = string(payload, "hover"); | ||
447 | +// if (!isNullOrEmpty(hover)) { | ||
448 | +// // If there is a hover node, include it in the selection and find intents. | ||
449 | +// processHoverExtendedSelection(sid, hover); | ||
450 | +// } | ||
451 | + } | ||
452 | + | ||
453 | + private boolean haveSelectedIntents() { | ||
454 | + return selectedIntents != null && !selectedIntents.isEmpty(); | ||
455 | + } | ||
456 | + | ||
457 | + // Processes the selection extended with hovered item to segregate items | ||
458 | + // into primary (those including the hover) vs secondary highlights. | ||
459 | + private void processHoverExtendedSelection(long sid, String hover) { | ||
460 | + Set<Host> hoverSelHosts = new HashSet<>(selectedHosts); | ||
461 | + Set<Device> hoverSelDevices = new HashSet<>(selectedDevices); | ||
462 | + addHover(hoverSelHosts, hoverSelDevices, hover); | ||
463 | + | ||
464 | + List<Intent> primary = selectedIntents == null ? new ArrayList<>() : | ||
465 | + intentFilter.findPathIntents(hoverSelHosts, hoverSelDevices, | ||
466 | + selectedIntents); | ||
467 | + Set<Intent> secondary = new HashSet<>(selectedIntents); | ||
468 | + secondary.removeAll(primary); | ||
469 | + | ||
470 | + // Send a message to highlight all links of all monitored intents. | ||
471 | + sendMessage(trafficMessage(sid, new TrafficClass("primary", primary), | ||
472 | + new TrafficClass("secondary", secondary))); | ||
473 | + } | ||
474 | + | ||
475 | + // Requests next or previous related intent. | ||
476 | + private void requestAnotherRelatedIntent(ObjectNode event, int offset) { | ||
477 | + if (haveSelectedIntents()) { | ||
478 | + currentIntentIndex = currentIntentIndex + offset; | ||
479 | + if (currentIntentIndex < 0) { | ||
480 | + currentIntentIndex = selectedIntents.size() - 1; | ||
481 | + } else if (currentIntentIndex >= selectedIntents.size()) { | ||
482 | + currentIntentIndex = 0; | ||
483 | + } | ||
484 | + sendSelectedIntent(event); | ||
485 | + } | ||
486 | + } | ||
487 | + | ||
488 | + // Sends traffic information on the related intents with the currently | ||
489 | + // selected intent highlighted. | ||
490 | + private void sendSelectedIntent(ObjectNode event) { | ||
491 | + Intent selectedIntent = selectedIntents.get(currentIntentIndex); | ||
492 | + log.info("Requested next intent {}", selectedIntent.id()); | ||
493 | + | ||
494 | + Set<Intent> primary = new HashSet<>(); | ||
495 | + primary.add(selectedIntent); | ||
496 | + | ||
497 | + Set<Intent> secondary = new HashSet<>(selectedIntents); | ||
498 | + secondary.remove(selectedIntent); | ||
499 | + | ||
500 | + // Send a message to highlight all links of the selected intent. | ||
501 | + sendMessage(trafficMessage(number(event, "sid"), | ||
502 | + new TrafficClass("primary", primary), | ||
503 | + new TrafficClass("secondary", secondary))); | ||
504 | + } | ||
505 | + | ||
506 | + // Requests monitoring of traffic for the selected intent. | ||
507 | + private void requestSelectedIntentTraffic(ObjectNode event) { | ||
508 | + if (haveSelectedIntents()) { | ||
509 | + if (currentIntentIndex < 0) { | ||
510 | + currentIntentIndex = 0; | ||
511 | + } | ||
512 | + Intent selectedIntent = selectedIntents.get(currentIntentIndex); | ||
513 | + log.info("Requested traffic for selected {}", selectedIntent.id()); | ||
514 | + | ||
515 | + Set<Intent> primary = new HashSet<>(); | ||
516 | + primary.add(selectedIntent); | ||
517 | + | ||
518 | + // Send a message to highlight all links of the selected intent. | ||
519 | + sendMessage(trafficMessage(number(event, "sid"), | ||
520 | + new TrafficClass("primary", primary, true))); | ||
521 | + } | ||
522 | + } | ||
523 | + | ||
524 | + // Cancels sending traffic messages. | ||
525 | + private void cancelTraffic(ObjectNode event) { | ||
526 | + selectedIntents = null; | ||
527 | + sendMessage(trafficMessage(number(event, "sid"))); | ||
528 | + stopTrafficMonitoring(); | ||
529 | + } | ||
530 | + | ||
531 | + | ||
532 | + private synchronized long startSummaryMonitoring(ObjectNode event) { | ||
533 | + stopSummaryMonitoring(); | ||
534 | + summaryEvent = event; | ||
535 | + summaryTask = new SummaryMonitor(); | ||
536 | + timer.schedule(summaryTask, SUMMARY_FREQUENCY, SUMMARY_FREQUENCY); | ||
537 | + return number(event, "sid"); | ||
538 | + } | ||
539 | + | ||
540 | + private synchronized void stopSummaryMonitoring() { | ||
541 | + if (summaryEvent != null) { | ||
542 | + summaryTask.cancel(); | ||
543 | + summaryTask = null; | ||
544 | + summaryEvent = null; | ||
545 | + } | ||
546 | + } | ||
547 | + | ||
548 | + // Subscribes for summary messages. | ||
549 | + private synchronized void requestSummary(ObjectNode event) { | ||
550 | + sendMessage(summmaryMessage(number(event, "sid"))); | ||
551 | + } | ||
552 | + | ||
553 | + | ||
554 | + // Forces mastership role rebalancing. | ||
555 | + private void equalizeMasters(ObjectNode event) { | ||
556 | + directory.get(MastershipAdminService.class).balanceRoles(); | ||
557 | + } | ||
558 | + | ||
559 | + | ||
560 | + // Adds all internal listeners. | ||
561 | + private void addListeners() { | ||
562 | + clusterService.addListener(clusterListener); | ||
563 | + mastershipService.addListener(mastershipListener); | ||
564 | + deviceService.addListener(deviceListener); | ||
565 | + linkService.addListener(linkListener); | ||
566 | + hostService.addListener(hostListener); | ||
567 | + intentService.addListener(intentListener); | ||
568 | + flowService.addListener(flowListener); | ||
569 | + } | ||
570 | + | ||
571 | + // Removes all internal listeners. | ||
572 | + private synchronized void removeListeners() { | ||
573 | + if (!listenersRemoved) { | ||
574 | + listenersRemoved = true; | ||
575 | + clusterService.removeListener(clusterListener); | ||
576 | + mastershipService.removeListener(mastershipListener); | ||
577 | + deviceService.removeListener(deviceListener); | ||
578 | + linkService.removeListener(linkListener); | ||
579 | + hostService.removeListener(hostListener); | ||
580 | + intentService.removeListener(intentListener); | ||
581 | + flowService.removeListener(flowListener); | ||
582 | + } | ||
583 | + } | ||
584 | + | ||
585 | + // Cluster event listener. | ||
586 | + private class InternalClusterListener implements ClusterEventListener { | ||
587 | + @Override | ||
588 | + public void event(ClusterEvent event) { | ||
589 | + sendMessage(instanceMessage(event, null)); | ||
590 | + } | ||
591 | + } | ||
592 | + | ||
593 | + // Mastership change listener | ||
594 | + private class InternalMastershipListener implements MastershipListener { | ||
595 | + @Override | ||
596 | + public void event(MastershipEvent event) { | ||
597 | + sendAllInstances("updateInstance"); | ||
598 | + Device device = deviceService.getDevice(event.subject()); | ||
599 | + sendMessage(deviceMessage(new DeviceEvent(DEVICE_UPDATED, device))); | ||
600 | + } | ||
601 | + } | ||
602 | + | ||
603 | + // Device event listener. | ||
604 | + private class InternalDeviceListener implements DeviceListener { | ||
605 | + @Override | ||
606 | + public void event(DeviceEvent event) { | ||
607 | + sendMessage(deviceMessage(event)); | ||
608 | + eventAccummulator.add(event); | ||
609 | + } | ||
610 | + } | ||
611 | + | ||
612 | + // Link event listener. | ||
613 | + private class InternalLinkListener implements LinkListener { | ||
614 | + @Override | ||
615 | + public void event(LinkEvent event) { | ||
616 | + sendMessage(linkMessage(event)); | ||
617 | + eventAccummulator.add(event); | ||
618 | + } | ||
619 | + } | ||
620 | + | ||
621 | + // Host event listener. | ||
622 | + private class InternalHostListener implements HostListener { | ||
623 | + @Override | ||
624 | + public void event(HostEvent event) { | ||
625 | + sendMessage(hostMessage(event)); | ||
626 | + eventAccummulator.add(event); | ||
627 | + } | ||
628 | + } | ||
629 | + | ||
630 | + // Intent event listener. | ||
631 | + private class InternalIntentListener implements IntentListener { | ||
632 | + @Override | ||
633 | + public void event(IntentEvent event) { | ||
634 | + if (trafficEvent != null) { | ||
635 | + requestSelectedIntentTraffic(trafficEvent); | ||
636 | + } | ||
637 | + eventAccummulator.add(event); | ||
638 | + } | ||
639 | + } | ||
640 | + | ||
641 | + // Intent event listener. | ||
642 | + private class InternalFlowListener implements FlowRuleListener { | ||
643 | + @Override | ||
644 | + public void event(FlowRuleEvent event) { | ||
645 | + eventAccummulator.add(event); | ||
646 | + } | ||
647 | + } | ||
648 | + | ||
649 | + // Periodic update of the traffic information | ||
650 | + private class TrafficMonitor extends TimerTask { | ||
651 | + @Override | ||
652 | + public void run() { | ||
653 | + try { | ||
654 | + if (trafficEvent != null) { | ||
655 | + String type = string(trafficEvent, "event", "unknown"); | ||
656 | + if (type.equals("requestAllTraffic")) { | ||
657 | + requestAllTraffic(trafficEvent); | ||
658 | + } else if (type.equals("requestDeviceLinkFlows")) { | ||
659 | + requestDeviceLinkFlows(trafficEvent); | ||
660 | + } else if (type.equals("requestSelectedIntentTraffic")) { | ||
661 | + requestSelectedIntentTraffic(trafficEvent); | ||
662 | + } | ||
663 | + } | ||
664 | + } catch (Exception e) { | ||
665 | + log.warn("Unable to handle traffic request due to {}", e.getMessage()); | ||
666 | + log.warn("Boom!", e); | ||
667 | + } | ||
668 | + } | ||
669 | + } | ||
670 | + | ||
671 | + // Periodic update of the summary information | ||
672 | + private class SummaryMonitor extends TimerTask { | ||
673 | + @Override | ||
674 | + public void run() { | ||
675 | + try { | ||
676 | + if (summaryEvent != null) { | ||
677 | + requestSummary(summaryEvent); | ||
678 | + } | ||
679 | + } catch (Exception e) { | ||
680 | + log.warn("Unable to handle summary request due to {}", e.getMessage()); | ||
681 | + log.warn("Boom!", e); | ||
682 | + } | ||
683 | + } | ||
684 | + } | ||
685 | + | ||
686 | + // Accumulates events to drive methodic update of the summary pane. | ||
687 | + private class InternalEventAccummulator extends AbstractAccumulator<Event> { | ||
688 | + protected InternalEventAccummulator() { | ||
689 | + super(new Timer("topo-summary"), MAX_EVENTS, MAX_BATCH_MS, MAX_IDLE_MS); | ||
690 | + } | ||
691 | + | ||
692 | + @Override | ||
693 | + public void processItems(List<Event> items) { | ||
694 | + try { | ||
695 | + if (summaryEvent != null) { | ||
696 | + sendMessage(summmaryMessage(0)); | ||
697 | + } | ||
698 | + } catch (Exception e) { | ||
699 | + log.warn("Unable to handle summary request due to {}", e.getMessage()); | ||
700 | + log.debug("Boom!", e); | ||
701 | + } | ||
702 | + } | ||
703 | + } | ||
704 | +} | ||
705 | + |
1 | +/* | ||
2 | + * Copyright 2015 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 | + */ | ||
16 | +package org.onosproject.ui.impl; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
20 | +import com.fasterxml.jackson.databind.node.ArrayNode; | ||
21 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
22 | +import org.onlab.osgi.ServiceDirectory; | ||
23 | +import org.onlab.packet.IpAddress; | ||
24 | +import org.onosproject.cluster.ClusterEvent; | ||
25 | +import org.onosproject.cluster.ClusterService; | ||
26 | +import org.onosproject.cluster.ControllerNode; | ||
27 | +import org.onosproject.cluster.NodeId; | ||
28 | +import org.onosproject.core.CoreService; | ||
29 | +import org.onosproject.mastership.MastershipService; | ||
30 | +import org.onosproject.net.Annotated; | ||
31 | +import org.onosproject.net.AnnotationKeys; | ||
32 | +import org.onosproject.net.Annotations; | ||
33 | +import org.onosproject.net.ConnectPoint; | ||
34 | +import org.onosproject.net.DefaultEdgeLink; | ||
35 | +import org.onosproject.net.Device; | ||
36 | +import org.onosproject.net.DeviceId; | ||
37 | +import org.onosproject.net.EdgeLink; | ||
38 | +import org.onosproject.net.Host; | ||
39 | +import org.onosproject.net.HostId; | ||
40 | +import org.onosproject.net.HostLocation; | ||
41 | +import org.onosproject.net.Link; | ||
42 | +import org.onosproject.net.LinkKey; | ||
43 | +import org.onosproject.net.PortNumber; | ||
44 | +import org.onosproject.net.device.DeviceEvent; | ||
45 | +import org.onosproject.net.device.DeviceService; | ||
46 | +import org.onosproject.net.flow.FlowEntry; | ||
47 | +import org.onosproject.net.flow.FlowRuleService; | ||
48 | +import org.onosproject.net.flow.TrafficTreatment; | ||
49 | +import org.onosproject.net.flow.instructions.Instruction; | ||
50 | +import org.onosproject.net.flow.instructions.Instructions.OutputInstruction; | ||
51 | +import org.onosproject.net.host.HostEvent; | ||
52 | +import org.onosproject.net.host.HostService; | ||
53 | +import org.onosproject.net.intent.Intent; | ||
54 | +import org.onosproject.net.intent.IntentService; | ||
55 | +import org.onosproject.net.intent.LinkCollectionIntent; | ||
56 | +import org.onosproject.net.intent.OpticalConnectivityIntent; | ||
57 | +import org.onosproject.net.intent.OpticalPathIntent; | ||
58 | +import org.onosproject.net.intent.PathIntent; | ||
59 | +import org.onosproject.net.link.LinkEvent; | ||
60 | +import org.onosproject.net.link.LinkService; | ||
61 | +import org.onosproject.net.provider.ProviderId; | ||
62 | +import org.onosproject.net.statistic.Load; | ||
63 | +import org.onosproject.net.statistic.StatisticService; | ||
64 | +import org.onosproject.net.topology.Topology; | ||
65 | +import org.onosproject.net.topology.TopologyService; | ||
66 | +import org.onosproject.ui.UiConnection; | ||
67 | +import org.onosproject.ui.UiMessageHandler; | ||
68 | +import org.slf4j.Logger; | ||
69 | +import org.slf4j.LoggerFactory; | ||
70 | + | ||
71 | +import java.text.DecimalFormat; | ||
72 | +import java.util.ArrayList; | ||
73 | +import java.util.Collection; | ||
74 | +import java.util.Collections; | ||
75 | +import java.util.HashMap; | ||
76 | +import java.util.HashSet; | ||
77 | +import java.util.Iterator; | ||
78 | +import java.util.List; | ||
79 | +import java.util.Map; | ||
80 | +import java.util.Set; | ||
81 | +import java.util.concurrent.ConcurrentHashMap; | ||
82 | + | ||
83 | +import static com.google.common.base.Preconditions.checkNotNull; | ||
84 | +import static com.google.common.base.Strings.isNullOrEmpty; | ||
85 | +import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED; | ||
86 | +import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED; | ||
87 | +import static org.onosproject.cluster.ControllerNode.State.ACTIVE; | ||
88 | +import static org.onosproject.net.DeviceId.deviceId; | ||
89 | +import static org.onosproject.net.HostId.hostId; | ||
90 | +import static org.onosproject.net.LinkKey.linkKey; | ||
91 | +import static org.onosproject.net.PortNumber.P0; | ||
92 | +import static org.onosproject.net.PortNumber.portNumber; | ||
93 | +import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED; | ||
94 | +import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED; | ||
95 | +import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED; | ||
96 | +import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED; | ||
97 | +import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED; | ||
98 | +import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED; | ||
99 | + | ||
100 | +/** | ||
101 | + * Facility for creating messages bound for the topology viewer. | ||
102 | + */ | ||
103 | +public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { | ||
104 | + | ||
105 | + protected static final Logger log = LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class); | ||
106 | + | ||
107 | + private static final ProviderId PID = new ProviderId("core", "org.onosproject.core", true); | ||
108 | + private static final String COMPACT = "%s/%s-%s/%s"; | ||
109 | + | ||
110 | + private static final double KB = 1024; | ||
111 | + private static final double MB = 1024 * KB; | ||
112 | + private static final double GB = 1024 * MB; | ||
113 | + | ||
114 | + private static final String GB_UNIT = "GB"; | ||
115 | + private static final String MB_UNIT = "MB"; | ||
116 | + private static final String KB_UNIT = "KB"; | ||
117 | + private static final String B_UNIT = "B"; | ||
118 | + | ||
119 | + protected ServiceDirectory directory; | ||
120 | + protected ClusterService clusterService; | ||
121 | + protected DeviceService deviceService; | ||
122 | + protected LinkService linkService; | ||
123 | + protected HostService hostService; | ||
124 | + protected MastershipService mastershipService; | ||
125 | + protected IntentService intentService; | ||
126 | + protected FlowRuleService flowService; | ||
127 | + protected StatisticService statService; | ||
128 | + protected TopologyService topologyService; | ||
129 | + | ||
130 | + protected final ObjectMapper mapper = new ObjectMapper(); | ||
131 | + private String version; | ||
132 | + | ||
133 | + // TODO: extract into an external & durable state; good enough for now and demo | ||
134 | + private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>(); | ||
135 | + | ||
136 | + /** | ||
137 | + * Creates a new message handler for the specified set of message types. | ||
138 | + * | ||
139 | + * @param messageTypes set of message types | ||
140 | + */ | ||
141 | + protected TopologyViewMessageHandlerBase(Set<String> messageTypes) { | ||
142 | + super(messageTypes); | ||
143 | + } | ||
144 | + | ||
145 | + /** | ||
146 | + * Returns read-only view of the meta-ui information. | ||
147 | + * | ||
148 | + * @return map of id to meta-ui mementos | ||
149 | + */ | ||
150 | + static Map<String, ObjectNode> getMetaUi() { | ||
151 | + return Collections.unmodifiableMap(metaUi); | ||
152 | + } | ||
153 | + | ||
154 | + @Override | ||
155 | + public void init(UiConnection connection, ServiceDirectory directory) { | ||
156 | + super.init(connection, directory); | ||
157 | + this.directory = checkNotNull(directory, "Directory cannot be null"); | ||
158 | + clusterService = directory.get(ClusterService.class); | ||
159 | + deviceService = directory.get(DeviceService.class); | ||
160 | + linkService = directory.get(LinkService.class); | ||
161 | + hostService = directory.get(HostService.class); | ||
162 | + mastershipService = directory.get(MastershipService.class); | ||
163 | + intentService = directory.get(IntentService.class); | ||
164 | + flowService = directory.get(FlowRuleService.class); | ||
165 | + statService = directory.get(StatisticService.class); | ||
166 | + topologyService = directory.get(TopologyService.class); | ||
167 | + | ||
168 | + String ver = directory.get(CoreService.class).version().toString(); | ||
169 | + version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", ""); | ||
170 | + } | ||
171 | + | ||
172 | + // Retrieves the payload from the specified event. | ||
173 | + protected ObjectNode payload(ObjectNode event) { | ||
174 | + return (ObjectNode) event.path("payload"); | ||
175 | + } | ||
176 | + | ||
177 | + // Returns the specified node property as a number | ||
178 | + protected long number(ObjectNode node, String name) { | ||
179 | + return node.path(name).asLong(); | ||
180 | + } | ||
181 | + | ||
182 | + // Returns the specified node property as a string. | ||
183 | + protected String string(ObjectNode node, String name) { | ||
184 | + return node.path(name).asText(); | ||
185 | + } | ||
186 | + | ||
187 | + // Returns the specified node property as a string. | ||
188 | + protected String string(ObjectNode node, String name, String defaultValue) { | ||
189 | + return node.path(name).asText(defaultValue); | ||
190 | + } | ||
191 | + | ||
192 | + // Returns the specified set of IP addresses as a string. | ||
193 | + private String ip(Set<IpAddress> ipAddresses) { | ||
194 | + Iterator<IpAddress> it = ipAddresses.iterator(); | ||
195 | + return it.hasNext() ? it.next().toString() : "unknown"; | ||
196 | + } | ||
197 | + | ||
198 | + // Produces JSON structure from annotations. | ||
199 | + private JsonNode props(Annotations annotations) { | ||
200 | + ObjectNode props = mapper.createObjectNode(); | ||
201 | + if (annotations != null) { | ||
202 | + for (String key : annotations.keys()) { | ||
203 | + props.put(key, annotations.value(key)); | ||
204 | + } | ||
205 | + } | ||
206 | + return props; | ||
207 | + } | ||
208 | + | ||
209 | + // Produces an informational log message event bound to the client. | ||
210 | + protected ObjectNode info(long id, String message) { | ||
211 | + return message("info", id, message); | ||
212 | + } | ||
213 | + | ||
214 | + // Produces a warning log message event bound to the client. | ||
215 | + protected ObjectNode warning(long id, String message) { | ||
216 | + return message("warning", id, message); | ||
217 | + } | ||
218 | + | ||
219 | + // Produces an error log message event bound to the client. | ||
220 | + protected ObjectNode error(long id, String message) { | ||
221 | + return message("error", id, message); | ||
222 | + } | ||
223 | + | ||
224 | + // Produces a log message event bound to the client. | ||
225 | + private ObjectNode message(String severity, long id, String message) { | ||
226 | + return envelope("message", id, | ||
227 | + mapper.createObjectNode() | ||
228 | + .put("severity", severity) | ||
229 | + .put("message", message)); | ||
230 | + } | ||
231 | + | ||
232 | + // Puts the payload into an envelope and returns it. | ||
233 | + protected ObjectNode envelope(String type, long sid, ObjectNode payload) { | ||
234 | + ObjectNode event = mapper.createObjectNode(); | ||
235 | + event.put("event", type); | ||
236 | + if (sid > 0) { | ||
237 | + event.put("sid", sid); | ||
238 | + } | ||
239 | + event.set("payload", payload); | ||
240 | + return event; | ||
241 | + } | ||
242 | + | ||
243 | + // Produces a set of all hosts listed in the specified JSON array. | ||
244 | + protected Set<Host> getHosts(ArrayNode array) { | ||
245 | + Set<Host> hosts = new HashSet<>(); | ||
246 | + if (array != null) { | ||
247 | + for (JsonNode node : array) { | ||
248 | + try { | ||
249 | + addHost(hosts, hostId(node.asText())); | ||
250 | + } catch (IllegalArgumentException e) { | ||
251 | + log.debug("Skipping ID {}", node.asText()); | ||
252 | + } | ||
253 | + } | ||
254 | + } | ||
255 | + return hosts; | ||
256 | + } | ||
257 | + | ||
258 | + // Adds the specified host to the set of hosts. | ||
259 | + private void addHost(Set<Host> hosts, HostId hostId) { | ||
260 | + Host host = hostService.getHost(hostId); | ||
261 | + if (host != null) { | ||
262 | + hosts.add(host); | ||
263 | + } | ||
264 | + } | ||
265 | + | ||
266 | + | ||
267 | + // Produces a set of all devices listed in the specified JSON array. | ||
268 | + protected Set<Device> getDevices(ArrayNode array) { | ||
269 | + Set<Device> devices = new HashSet<>(); | ||
270 | + if (array != null) { | ||
271 | + for (JsonNode node : array) { | ||
272 | + try { | ||
273 | + addDevice(devices, deviceId(node.asText())); | ||
274 | + } catch (IllegalArgumentException e) { | ||
275 | + log.debug("Skipping ID {}", node.asText()); | ||
276 | + } | ||
277 | + } | ||
278 | + } | ||
279 | + return devices; | ||
280 | + } | ||
281 | + | ||
282 | + private void addDevice(Set<Device> devices, DeviceId deviceId) { | ||
283 | + Device device = deviceService.getDevice(deviceId); | ||
284 | + if (device != null) { | ||
285 | + devices.add(device); | ||
286 | + } | ||
287 | + } | ||
288 | + | ||
289 | + protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) { | ||
290 | + try { | ||
291 | + addHost(hosts, hostId(hover)); | ||
292 | + } catch (IllegalArgumentException e) { | ||
293 | + try { | ||
294 | + addDevice(devices, deviceId(hover)); | ||
295 | + } catch (IllegalArgumentException ne) { | ||
296 | + log.debug("Skipping ID {}", hover); | ||
297 | + } | ||
298 | + } | ||
299 | + } | ||
300 | + | ||
301 | + // Produces a cluster instance message to the client. | ||
302 | + protected ObjectNode instanceMessage(ClusterEvent event, String messageType) { | ||
303 | + ControllerNode node = event.subject(); | ||
304 | + int switchCount = mastershipService.getDevicesOf(node.id()).size(); | ||
305 | + ObjectNode payload = mapper.createObjectNode() | ||
306 | + .put("id", node.id().toString()) | ||
307 | + .put("ip", node.ip().toString()) | ||
308 | + .put("online", clusterService.getState(node.id()) == ACTIVE) | ||
309 | + .put("uiAttached", event.subject().equals(clusterService.getLocalNode())) | ||
310 | + .put("switches", switchCount); | ||
311 | + | ||
312 | + ArrayNode labels = mapper.createArrayNode(); | ||
313 | + labels.add(node.id().toString()); | ||
314 | + labels.add(node.ip().toString()); | ||
315 | + | ||
316 | + // Add labels, props and stuff the payload into envelope. | ||
317 | + payload.set("labels", labels); | ||
318 | + addMetaUi(node.id().toString(), payload); | ||
319 | + | ||
320 | + String type = messageType != null ? messageType : | ||
321 | + ((event.type() == INSTANCE_ADDED) ? "addInstance" : | ||
322 | + ((event.type() == INSTANCE_REMOVED ? "removeInstance" : | ||
323 | + "addInstance"))); | ||
324 | + return envelope(type, 0, payload); | ||
325 | + } | ||
326 | + | ||
327 | + // Produces a device event message to the client. | ||
328 | + protected ObjectNode deviceMessage(DeviceEvent event) { | ||
329 | + Device device = event.subject(); | ||
330 | + ObjectNode payload = mapper.createObjectNode() | ||
331 | + .put("id", device.id().toString()) | ||
332 | + .put("type", device.type().toString().toLowerCase()) | ||
333 | + .put("online", deviceService.isAvailable(device.id())) | ||
334 | + .put("master", master(device.id())); | ||
335 | + | ||
336 | + // Generate labels: id, chassis id, no-label, optional-name | ||
337 | + String name = device.annotations().value(AnnotationKeys.NAME); | ||
338 | + ArrayNode labels = mapper.createArrayNode(); | ||
339 | + labels.add(""); | ||
340 | + labels.add(isNullOrEmpty(name) ? device.id().toString() : name); | ||
341 | + labels.add(device.id().toString()); | ||
342 | + | ||
343 | + // Add labels, props and stuff the payload into envelope. | ||
344 | + payload.set("labels", labels); | ||
345 | + payload.set("props", props(device.annotations())); | ||
346 | + addGeoLocation(device, payload); | ||
347 | + addMetaUi(device.id().toString(), payload); | ||
348 | + | ||
349 | + String type = (event.type() == DEVICE_ADDED) ? "addDevice" : | ||
350 | + ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice"); | ||
351 | + return envelope(type, 0, payload); | ||
352 | + } | ||
353 | + | ||
354 | + // Produces a link event message to the client. | ||
355 | + protected ObjectNode linkMessage(LinkEvent event) { | ||
356 | + Link link = event.subject(); | ||
357 | + ObjectNode payload = mapper.createObjectNode() | ||
358 | + .put("id", compactLinkString(link)) | ||
359 | + .put("type", link.type().toString().toLowerCase()) | ||
360 | + .put("online", link.state() == Link.State.ACTIVE) | ||
361 | + .put("linkWidth", 1.2) | ||
362 | + .put("src", link.src().deviceId().toString()) | ||
363 | + .put("srcPort", link.src().port().toString()) | ||
364 | + .put("dst", link.dst().deviceId().toString()) | ||
365 | + .put("dstPort", link.dst().port().toString()); | ||
366 | + String type = (event.type() == LINK_ADDED) ? "addLink" : | ||
367 | + ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink"); | ||
368 | + return envelope(type, 0, payload); | ||
369 | + } | ||
370 | + | ||
371 | + // Produces a host event message to the client. | ||
372 | + protected ObjectNode hostMessage(HostEvent event) { | ||
373 | + Host host = event.subject(); | ||
374 | + String hostType = host.annotations().value(AnnotationKeys.TYPE); | ||
375 | + ObjectNode payload = mapper.createObjectNode() | ||
376 | + .put("id", host.id().toString()) | ||
377 | + .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType) | ||
378 | + .put("ingress", compactLinkString(edgeLink(host, true))) | ||
379 | + .put("egress", compactLinkString(edgeLink(host, false))); | ||
380 | + payload.set("cp", hostConnect(mapper, host.location())); | ||
381 | + payload.set("labels", labels(mapper, ip(host.ipAddresses()), | ||
382 | + host.mac().toString())); | ||
383 | + payload.set("props", props(host.annotations())); | ||
384 | + addGeoLocation(host, payload); | ||
385 | + addMetaUi(host.id().toString(), payload); | ||
386 | + | ||
387 | + String type = (event.type() == HOST_ADDED) ? "addHost" : | ||
388 | + ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost"); | ||
389 | + return envelope(type, 0, payload); | ||
390 | + } | ||
391 | + | ||
392 | + // Encodes the specified host location into a JSON object. | ||
393 | + private ObjectNode hostConnect(ObjectMapper mapper, HostLocation location) { | ||
394 | + return mapper.createObjectNode() | ||
395 | + .put("device", location.deviceId().toString()) | ||
396 | + .put("port", location.port().toLong()); | ||
397 | + } | ||
398 | + | ||
399 | + // Encodes the specified list of labels a JSON array. | ||
400 | + private ArrayNode labels(ObjectMapper mapper, String... labels) { | ||
401 | + ArrayNode json = mapper.createArrayNode(); | ||
402 | + for (String label : labels) { | ||
403 | + json.add(label); | ||
404 | + } | ||
405 | + return json; | ||
406 | + } | ||
407 | + | ||
408 | + // Returns the name of the master node for the specified device id. | ||
409 | + private String master(DeviceId deviceId) { | ||
410 | + NodeId master = mastershipService.getMasterFor(deviceId); | ||
411 | + return master != null ? master.toString() : ""; | ||
412 | + } | ||
413 | + | ||
414 | + // Generates an edge link from the specified host location. | ||
415 | + private EdgeLink edgeLink(Host host, boolean ingress) { | ||
416 | + return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)), | ||
417 | + host.location(), ingress); | ||
418 | + } | ||
419 | + | ||
420 | + // Adds meta UI information for the specified object. | ||
421 | + private void addMetaUi(String id, ObjectNode payload) { | ||
422 | + ObjectNode meta = metaUi.get(id); | ||
423 | + if (meta != null) { | ||
424 | + payload.set("metaUi", meta); | ||
425 | + } | ||
426 | + } | ||
427 | + | ||
428 | + // Adds a geo location JSON to the specified payload object. | ||
429 | + private void addGeoLocation(Annotated annotated, ObjectNode payload) { | ||
430 | + Annotations annotations = annotated.annotations(); | ||
431 | + if (annotations == null) { | ||
432 | + return; | ||
433 | + } | ||
434 | + | ||
435 | + String slat = annotations.value(AnnotationKeys.LATITUDE); | ||
436 | + String slng = annotations.value(AnnotationKeys.LONGITUDE); | ||
437 | + try { | ||
438 | + if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) { | ||
439 | + double lat = Double.parseDouble(slat); | ||
440 | + double lng = Double.parseDouble(slng); | ||
441 | + ObjectNode loc = mapper.createObjectNode() | ||
442 | + .put("type", "latlng").put("lat", lat).put("lng", lng); | ||
443 | + payload.set("location", loc); | ||
444 | + } | ||
445 | + } catch (NumberFormatException e) { | ||
446 | + log.warn("Invalid geo data latitude={}; longiture={}", slat, slng); | ||
447 | + } | ||
448 | + } | ||
449 | + | ||
450 | + // Updates meta UI information for the specified object. | ||
451 | + protected void updateMetaUi(ObjectNode event) { | ||
452 | + ObjectNode payload = payload(event); | ||
453 | + metaUi.put(string(payload, "id"), (ObjectNode) payload.path("memento")); | ||
454 | + } | ||
455 | + | ||
456 | + // Returns summary response. | ||
457 | + protected ObjectNode summmaryMessage(long sid) { | ||
458 | + Topology topology = topologyService.currentTopology(); | ||
459 | + return envelope("showSummary", sid, | ||
460 | + json("ONOS Summary", "node", | ||
461 | + new Prop("Devices", format(topology.deviceCount())), | ||
462 | + new Prop("Links", format(topology.linkCount())), | ||
463 | + new Prop("Hosts", format(hostService.getHostCount())), | ||
464 | + new Prop("Topology SCCs", format(topology.clusterCount())), | ||
465 | + new Separator(), | ||
466 | + new Prop("Intents", format(intentService.getIntentCount())), | ||
467 | + new Prop("Flows", format(flowService.getFlowRuleCount())), | ||
468 | + new Prop("Version", version))); | ||
469 | + } | ||
470 | + | ||
471 | + // Returns device details response. | ||
472 | + protected ObjectNode deviceDetails(DeviceId deviceId, long sid) { | ||
473 | + Device device = deviceService.getDevice(deviceId); | ||
474 | + Annotations annot = device.annotations(); | ||
475 | + String name = annot.value(AnnotationKeys.NAME); | ||
476 | + int portCount = deviceService.getPorts(deviceId).size(); | ||
477 | + int flowCount = getFlowCount(deviceId); | ||
478 | + return envelope("showDetails", sid, | ||
479 | + json(isNullOrEmpty(name) ? deviceId.toString() : name, | ||
480 | + device.type().toString().toLowerCase(), | ||
481 | + new Prop("URI", deviceId.toString()), | ||
482 | + new Prop("Vendor", device.manufacturer()), | ||
483 | + new Prop("H/W Version", device.hwVersion()), | ||
484 | + new Prop("S/W Version", device.swVersion()), | ||
485 | + new Prop("Serial Number", device.serialNumber()), | ||
486 | + new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)), | ||
487 | + new Separator(), | ||
488 | + new Prop("Master", master(deviceId)), | ||
489 | + new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)), | ||
490 | + new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)), | ||
491 | + new Separator(), | ||
492 | + new Prop("Ports", Integer.toString(portCount)), | ||
493 | + new Prop("Flows", Integer.toString(flowCount)))); | ||
494 | + } | ||
495 | + | ||
496 | + protected int getFlowCount(DeviceId deviceId) { | ||
497 | + int count = 0; | ||
498 | + Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator(); | ||
499 | + while (it.hasNext()) { | ||
500 | + count++; | ||
501 | + it.next(); | ||
502 | + } | ||
503 | + return count; | ||
504 | + } | ||
505 | + | ||
506 | + // Counts all entries that egress on the given device links. | ||
507 | + protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) { | ||
508 | + List<FlowEntry> entries = new ArrayList<>(); | ||
509 | + Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId)); | ||
510 | + Set<Host> hosts = hostService.getConnectedHosts(deviceId); | ||
511 | + Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator(); | ||
512 | + while (it.hasNext()) { | ||
513 | + entries.add(it.next()); | ||
514 | + } | ||
515 | + | ||
516 | + // Add all edge links to the set | ||
517 | + if (hosts != null) { | ||
518 | + for (Host host : hosts) { | ||
519 | + links.add(new DefaultEdgeLink(host.providerId(), | ||
520 | + new ConnectPoint(host.id(), P0), | ||
521 | + host.location(), false)); | ||
522 | + } | ||
523 | + } | ||
524 | + | ||
525 | + Map<Link, Integer> counts = new HashMap<>(); | ||
526 | + for (Link link : links) { | ||
527 | + counts.put(link, getEgressFlows(link, entries)); | ||
528 | + } | ||
529 | + return counts; | ||
530 | + } | ||
531 | + | ||
532 | + // Counts all entries that egress on the link source port. | ||
533 | + private Integer getEgressFlows(Link link, List<FlowEntry> entries) { | ||
534 | + int count = 0; | ||
535 | + PortNumber out = link.src().port(); | ||
536 | + for (FlowEntry entry : entries) { | ||
537 | + TrafficTreatment treatment = entry.treatment(); | ||
538 | + for (Instruction instruction : treatment.instructions()) { | ||
539 | + if (instruction.type() == Instruction.Type.OUTPUT && | ||
540 | + ((OutputInstruction) instruction).port().equals(out)) { | ||
541 | + count++; | ||
542 | + } | ||
543 | + } | ||
544 | + } | ||
545 | + return count; | ||
546 | + } | ||
547 | + | ||
548 | + | ||
549 | + // Returns host details response. | ||
550 | + protected ObjectNode hostDetails(HostId hostId, long sid) { | ||
551 | + Host host = hostService.getHost(hostId); | ||
552 | + Annotations annot = host.annotations(); | ||
553 | + String type = annot.value(AnnotationKeys.TYPE); | ||
554 | + String name = annot.value(AnnotationKeys.NAME); | ||
555 | + String vlan = host.vlan().toString(); | ||
556 | + return envelope("showDetails", sid, | ||
557 | + json(isNullOrEmpty(name) ? hostId.toString() : name, | ||
558 | + isNullOrEmpty(type) ? "endstation" : type, | ||
559 | + new Prop("MAC", host.mac().toString()), | ||
560 | + new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")), | ||
561 | + new Prop("VLAN", vlan.equals("-1") ? "none" : vlan), | ||
562 | + new Separator(), | ||
563 | + new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)), | ||
564 | + new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)))); | ||
565 | + } | ||
566 | + | ||
567 | + | ||
568 | + // Produces JSON message to trigger traffic overview visualization | ||
569 | + protected ObjectNode trafficSummaryMessage(long sid) { | ||
570 | + ObjectNode payload = mapper.createObjectNode(); | ||
571 | + ArrayNode paths = mapper.createArrayNode(); | ||
572 | + payload.set("paths", paths); | ||
573 | + | ||
574 | + ObjectNode pathNodeN = mapper.createObjectNode(); | ||
575 | + ArrayNode linksNodeN = mapper.createArrayNode(); | ||
576 | + ArrayNode labelsN = mapper.createArrayNode(); | ||
577 | + | ||
578 | + pathNodeN.put("class", "plain").put("traffic", false); | ||
579 | + pathNodeN.set("links", linksNodeN); | ||
580 | + pathNodeN.set("labels", labelsN); | ||
581 | + paths.add(pathNodeN); | ||
582 | + | ||
583 | + ObjectNode pathNodeT = mapper.createObjectNode(); | ||
584 | + ArrayNode linksNodeT = mapper.createArrayNode(); | ||
585 | + ArrayNode labelsT = mapper.createArrayNode(); | ||
586 | + | ||
587 | + pathNodeT.put("class", "secondary").put("traffic", true); | ||
588 | + pathNodeT.set("links", linksNodeT); | ||
589 | + pathNodeT.set("labels", labelsT); | ||
590 | + paths.add(pathNodeT); | ||
591 | + | ||
592 | + for (BiLink link : consolidateLinks(linkService.getLinks())) { | ||
593 | + boolean bi = link.two != null; | ||
594 | + if (isInfrastructureEgress(link.one) || | ||
595 | + (bi && isInfrastructureEgress(link.two))) { | ||
596 | + link.addLoad(statService.load(link.one)); | ||
597 | + link.addLoad(bi ? statService.load(link.two) : null); | ||
598 | + if (link.hasTraffic) { | ||
599 | + linksNodeT.add(compactLinkString(link.one)); | ||
600 | + labelsT.add(formatBytes(link.bytes)); | ||
601 | + } else { | ||
602 | + linksNodeN.add(compactLinkString(link.one)); | ||
603 | + labelsN.add(""); | ||
604 | + } | ||
605 | + } | ||
606 | + } | ||
607 | + return envelope("showTraffic", sid, payload); | ||
608 | + } | ||
609 | + | ||
610 | + private Collection<BiLink> consolidateLinks(Iterable<Link> links) { | ||
611 | + Map<LinkKey, BiLink> biLinks = new HashMap<>(); | ||
612 | + for (Link link : links) { | ||
613 | + addLink(biLinks, link); | ||
614 | + } | ||
615 | + return biLinks.values(); | ||
616 | + } | ||
617 | + | ||
618 | + // Produces JSON message to trigger flow overview visualization | ||
619 | + protected ObjectNode flowSummaryMessage(long sid, Set<Device> devices) { | ||
620 | + ObjectNode payload = mapper.createObjectNode(); | ||
621 | + ArrayNode paths = mapper.createArrayNode(); | ||
622 | + payload.set("paths", paths); | ||
623 | + | ||
624 | + for (Device device : devices) { | ||
625 | + Map<Link, Integer> counts = getFlowCounts(device.id()); | ||
626 | + for (Link link : counts.keySet()) { | ||
627 | + addLinkFlows(link, paths, counts.get(link)); | ||
628 | + } | ||
629 | + } | ||
630 | + return envelope("showTraffic", sid, payload); | ||
631 | + } | ||
632 | + | ||
633 | + private void addLinkFlows(Link link, ArrayNode paths, Integer count) { | ||
634 | + ObjectNode pathNode = mapper.createObjectNode(); | ||
635 | + ArrayNode linksNode = mapper.createArrayNode(); | ||
636 | + ArrayNode labels = mapper.createArrayNode(); | ||
637 | + boolean noFlows = count == null || count == 0; | ||
638 | + pathNode.put("class", noFlows ? "secondary" : "primary"); | ||
639 | + pathNode.put("traffic", false); | ||
640 | + pathNode.set("links", linksNode.add(compactLinkString(link))); | ||
641 | + pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() + | ||
642 | + (count == 1 ? " flow" : " flows")))); | ||
643 | + paths.add(pathNode); | ||
644 | + } | ||
645 | + | ||
646 | + | ||
647 | + // Produces JSON message to trigger traffic visualization | ||
648 | + protected ObjectNode trafficMessage(long sid, TrafficClass... trafficClasses) { | ||
649 | + ObjectNode payload = mapper.createObjectNode(); | ||
650 | + ArrayNode paths = mapper.createArrayNode(); | ||
651 | + payload.set("paths", paths); | ||
652 | + | ||
653 | + // Classify links based on their traffic traffic first... | ||
654 | + Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses); | ||
655 | + | ||
656 | + // Then separate the links into their respective classes and send them out. | ||
657 | + Map<String, ObjectNode> pathNodes = new HashMap<>(); | ||
658 | + for (BiLink biLink : biLinks.values()) { | ||
659 | + boolean hasTraffic = biLink.hasTraffic; | ||
660 | + String tc = (biLink.classes + (hasTraffic ? " animated" : "")).trim(); | ||
661 | + ObjectNode pathNode = pathNodes.get(tc); | ||
662 | + if (pathNode == null) { | ||
663 | + pathNode = mapper.createObjectNode() | ||
664 | + .put("class", tc).put("traffic", hasTraffic); | ||
665 | + pathNode.set("links", mapper.createArrayNode()); | ||
666 | + pathNode.set("labels", mapper.createArrayNode()); | ||
667 | + pathNodes.put(tc, pathNode); | ||
668 | + paths.add(pathNode); | ||
669 | + } | ||
670 | + ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one)); | ||
671 | + ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : ""); | ||
672 | + } | ||
673 | + | ||
674 | + return envelope("showTraffic", sid, payload); | ||
675 | + } | ||
676 | + | ||
677 | + // Classifies the link traffic according to the specified classes. | ||
678 | + private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) { | ||
679 | + Map<LinkKey, BiLink> biLinks = new HashMap<>(); | ||
680 | + for (TrafficClass trafficClass : trafficClasses) { | ||
681 | + for (Intent intent : trafficClass.intents) { | ||
682 | + boolean isOptical = intent instanceof OpticalConnectivityIntent; | ||
683 | + List<Intent> installables = intentService.getInstallableIntents(intent.key()); | ||
684 | + if (installables != null) { | ||
685 | + for (Intent installable : installables) { | ||
686 | + String type = isOptical ? trafficClass.type + " optical" : trafficClass.type; | ||
687 | + if (installable instanceof PathIntent) { | ||
688 | + classifyLinks(type, biLinks, trafficClass.showTraffic, | ||
689 | + ((PathIntent) installable).path().links()); | ||
690 | + } else if (installable instanceof LinkCollectionIntent) { | ||
691 | + classifyLinks(type, biLinks, trafficClass.showTraffic, | ||
692 | + ((LinkCollectionIntent) installable).links()); | ||
693 | + } else if (installable instanceof OpticalPathIntent) { | ||
694 | + classifyLinks(type, biLinks, trafficClass.showTraffic, | ||
695 | + ((OpticalPathIntent) installable).path().links()); | ||
696 | + } | ||
697 | + } | ||
698 | + } | ||
699 | + } | ||
700 | + } | ||
701 | + return biLinks; | ||
702 | + } | ||
703 | + | ||
704 | + | ||
705 | + // Adds the link segments (path or tree) associated with the specified | ||
706 | + // connectivity intent | ||
707 | + private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks, | ||
708 | + boolean showTraffic, Iterable<Link> links) { | ||
709 | + if (links != null) { | ||
710 | + for (Link link : links) { | ||
711 | + BiLink biLink = addLink(biLinks, link); | ||
712 | + if (isInfrastructureEgress(link)) { | ||
713 | + if (showTraffic) { | ||
714 | + biLink.addLoad(statService.load(link)); | ||
715 | + } | ||
716 | + biLink.addClass(type); | ||
717 | + } | ||
718 | + } | ||
719 | + } | ||
720 | + } | ||
721 | + | ||
722 | + | ||
723 | + private BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) { | ||
724 | + LinkKey key = canonicalLinkKey(link); | ||
725 | + BiLink biLink = biLinks.get(key); | ||
726 | + if (biLink != null) { | ||
727 | + biLink.setOther(link); | ||
728 | + } else { | ||
729 | + biLink = new BiLink(key, link); | ||
730 | + biLinks.put(key, biLink); | ||
731 | + } | ||
732 | + return biLink; | ||
733 | + } | ||
734 | + | ||
735 | + | ||
736 | + // Adds the link segments (path or tree) associated with the specified | ||
737 | + // connectivity intent | ||
738 | + protected void addPathTraffic(ArrayNode paths, String type, String trafficType, | ||
739 | + Iterable<Link> links) { | ||
740 | + ObjectNode pathNode = mapper.createObjectNode(); | ||
741 | + ArrayNode linksNode = mapper.createArrayNode(); | ||
742 | + | ||
743 | + if (links != null) { | ||
744 | + ArrayNode labels = mapper.createArrayNode(); | ||
745 | + boolean hasTraffic = false; | ||
746 | + for (Link link : links) { | ||
747 | + if (isInfrastructureEgress(link)) { | ||
748 | + linksNode.add(compactLinkString(link)); | ||
749 | + Load load = statService.load(link); | ||
750 | + String label = ""; | ||
751 | + if (load.rate() > 0) { | ||
752 | + hasTraffic = true; | ||
753 | + label = formatBytes(load.latest()); | ||
754 | + } | ||
755 | + labels.add(label); | ||
756 | + } | ||
757 | + } | ||
758 | + pathNode.put("class", hasTraffic ? type + " " + trafficType : type); | ||
759 | + pathNode.put("traffic", hasTraffic); | ||
760 | + pathNode.set("links", linksNode); | ||
761 | + pathNode.set("labels", labels); | ||
762 | + paths.add(pathNode); | ||
763 | + } | ||
764 | + } | ||
765 | + | ||
766 | + // Poor-mans formatting to get the labels with byte counts looking nice. | ||
767 | + private String formatBytes(long bytes) { | ||
768 | + String unit; | ||
769 | + double value; | ||
770 | + if (bytes > GB) { | ||
771 | + value = bytes / GB; | ||
772 | + unit = GB_UNIT; | ||
773 | + } else if (bytes > MB) { | ||
774 | + value = bytes / MB; | ||
775 | + unit = MB_UNIT; | ||
776 | + } else if (bytes > KB) { | ||
777 | + value = bytes / KB; | ||
778 | + unit = KB_UNIT; | ||
779 | + } else { | ||
780 | + value = bytes; | ||
781 | + unit = B_UNIT; | ||
782 | + } | ||
783 | + DecimalFormat format = new DecimalFormat("#,###.##"); | ||
784 | + return format.format(value) + " " + unit; | ||
785 | + } | ||
786 | + | ||
787 | + // Formats the given number into a string. | ||
788 | + private String format(Number number) { | ||
789 | + DecimalFormat format = new DecimalFormat("#,###"); | ||
790 | + return format.format(number); | ||
791 | + } | ||
792 | + | ||
793 | + private boolean isInfrastructureEgress(Link link) { | ||
794 | + return link.src().elementId() instanceof DeviceId; | ||
795 | + } | ||
796 | + | ||
797 | + // Produces compact string representation of a link. | ||
798 | + private static String compactLinkString(Link link) { | ||
799 | + return String.format(COMPACT, link.src().elementId(), link.src().port(), | ||
800 | + link.dst().elementId(), link.dst().port()); | ||
801 | + } | ||
802 | + | ||
803 | + // Produces JSON property details. | ||
804 | + private ObjectNode json(String id, String type, Prop... props) { | ||
805 | + ObjectMapper mapper = new ObjectMapper(); | ||
806 | + ObjectNode result = mapper.createObjectNode() | ||
807 | + .put("id", id).put("type", type); | ||
808 | + ObjectNode pnode = mapper.createObjectNode(); | ||
809 | + ArrayNode porder = mapper.createArrayNode(); | ||
810 | + for (Prop p : props) { | ||
811 | + porder.add(p.key); | ||
812 | + pnode.put(p.key, p.value); | ||
813 | + } | ||
814 | + result.set("propOrder", porder); | ||
815 | + result.set("props", pnode); | ||
816 | + return result; | ||
817 | + } | ||
818 | + | ||
819 | + // Produces canonical link key, i.e. one that will match link and its inverse. | ||
820 | + private LinkKey canonicalLinkKey(Link link) { | ||
821 | + String sn = link.src().elementId().toString(); | ||
822 | + String dn = link.dst().elementId().toString(); | ||
823 | + return sn.compareTo(dn) < 0 ? | ||
824 | + linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src()); | ||
825 | + } | ||
826 | + | ||
827 | + // Representation of link and its inverse and any traffic data. | ||
828 | + private class BiLink { | ||
829 | + public final LinkKey key; | ||
830 | + public final Link one; | ||
831 | + public Link two; | ||
832 | + public boolean hasTraffic = false; | ||
833 | + public long bytes = 0; | ||
834 | + public String classes = ""; | ||
835 | + | ||
836 | + BiLink(LinkKey key, Link link) { | ||
837 | + this.key = key; | ||
838 | + this.one = link; | ||
839 | + } | ||
840 | + | ||
841 | + void setOther(Link link) { | ||
842 | + this.two = link; | ||
843 | + } | ||
844 | + | ||
845 | + void addLoad(Load load) { | ||
846 | + if (load != null) { | ||
847 | + this.hasTraffic = hasTraffic || load.rate() > 0; | ||
848 | + this.bytes += load.latest(); | ||
849 | + } | ||
850 | + } | ||
851 | + | ||
852 | + void addClass(String trafficClass) { | ||
853 | + classes = classes + " " + trafficClass; | ||
854 | + } | ||
855 | + } | ||
856 | + | ||
857 | + // Auxiliary key/value carrier. | ||
858 | + private class Prop { | ||
859 | + public final String key; | ||
860 | + public final String value; | ||
861 | + | ||
862 | + protected Prop(String key, String value) { | ||
863 | + this.key = key; | ||
864 | + this.value = value; | ||
865 | + } | ||
866 | + } | ||
867 | + | ||
868 | + // Auxiliary properties separator | ||
869 | + private class Separator extends Prop { | ||
870 | + protected Separator() { | ||
871 | + super("-", ""); | ||
872 | + } | ||
873 | + } | ||
874 | + | ||
875 | + // Auxiliary carrier of data for requesting traffic message. | ||
876 | + protected class TrafficClass { | ||
877 | + public final boolean showTraffic; | ||
878 | + public final String type; | ||
879 | + public final Iterable<Intent> intents; | ||
880 | + | ||
881 | + TrafficClass(String type, Iterable<Intent> intents) { | ||
882 | + this(type, intents, false); | ||
883 | + } | ||
884 | + | ||
885 | + TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) { | ||
886 | + this.type = type; | ||
887 | + this.intents = intents; | ||
888 | + this.showTraffic = showTraffic; | ||
889 | + } | ||
890 | + } | ||
891 | + | ||
892 | +} |
... | @@ -98,6 +98,7 @@ import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED; | ... | @@ -98,6 +98,7 @@ import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED; |
98 | /** | 98 | /** |
99 | * Facility for creating messages bound for the topology viewer. | 99 | * Facility for creating messages bound for the topology viewer. |
100 | */ | 100 | */ |
101 | +@Deprecated | ||
101 | public abstract class TopologyViewMessages { | 102 | public abstract class TopologyViewMessages { |
102 | 103 | ||
103 | protected static final Logger log = LoggerFactory.getLogger(TopologyViewMessages.class); | 104 | protected static final Logger log = LoggerFactory.getLogger(TopologyViewMessages.class); | ... | ... |
... | @@ -77,6 +77,7 @@ import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED; | ... | @@ -77,6 +77,7 @@ import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED; |
77 | /** | 77 | /** |
78 | * Web socket capable of interacting with the GUI topology view. | 78 | * Web socket capable of interacting with the GUI topology view. |
79 | */ | 79 | */ |
80 | +@Deprecated | ||
80 | public class TopologyViewWebSocket | 81 | public class TopologyViewWebSocket |
81 | extends TopologyViewMessages | 82 | extends TopologyViewMessages |
82 | implements WebSocket.OnTextMessage, WebSocket.OnControl { | 83 | implements WebSocket.OnTextMessage, WebSocket.OnControl { | ... | ... |
... | @@ -59,7 +59,7 @@ public class UiExtensionManager implements UiExtensionService { | ... | @@ -59,7 +59,7 @@ public class UiExtensionManager implements UiExtensionService { |
59 | List<UiView> coreViews = of(new UiView("sample", "Sample"), | 59 | List<UiView> coreViews = of(new UiView("sample", "Sample"), |
60 | new UiView("topo", "Topology View"), | 60 | new UiView("topo", "Topology View"), |
61 | new UiView("device", "Devices")); | 61 | new UiView("device", "Devices")); |
62 | - UiMessageHandlerFactory messageHandlerFactory = null; | 62 | + UiMessageHandlerFactory messageHandlerFactory = () -> ImmutableList.of(new TopologyViewMessageHandler()); |
63 | return new UiExtension(coreViews, messageHandlerFactory, "core", | 63 | return new UiExtension(coreViews, messageHandlerFactory, "core", |
64 | UiExtensionManager.class.getClassLoader()); | 64 | UiExtensionManager.class.getClassLoader()); |
65 | } | 65 | } | ... | ... |
... | @@ -22,6 +22,7 @@ import org.onlab.osgi.ServiceDirectory; | ... | @@ -22,6 +22,7 @@ import org.onlab.osgi.ServiceDirectory; |
22 | import org.onosproject.ui.UiConnection; | 22 | import org.onosproject.ui.UiConnection; |
23 | import org.onosproject.ui.UiExtensionService; | 23 | import org.onosproject.ui.UiExtensionService; |
24 | import org.onosproject.ui.UiMessageHandler; | 24 | import org.onosproject.ui.UiMessageHandler; |
25 | +import org.onosproject.ui.UiMessageHandlerFactory; | ||
25 | import org.slf4j.Logger; | 26 | import org.slf4j.Logger; |
26 | import org.slf4j.LoggerFactory; | 27 | import org.slf4j.LoggerFactory; |
27 | 28 | ||
... | @@ -117,7 +118,7 @@ public class UiWebSocket | ... | @@ -117,7 +118,7 @@ public class UiWebSocket |
117 | lastActive = System.currentTimeMillis(); | 118 | lastActive = System.currentTimeMillis(); |
118 | try { | 119 | try { |
119 | ObjectNode message = (ObjectNode) mapper.reader().readTree(data); | 120 | ObjectNode message = (ObjectNode) mapper.reader().readTree(data); |
120 | - String type = message.path("type").asText("unknown"); | 121 | + String type = message.path("event").asText("unknown"); |
121 | UiMessageHandler handler = handlers.get(type); | 122 | UiMessageHandler handler = handlers.get(type); |
122 | if (handler != null) { | 123 | if (handler != null) { |
123 | handler.process(message); | 124 | handler.process(message); |
... | @@ -146,10 +147,15 @@ public class UiWebSocket | ... | @@ -146,10 +147,15 @@ public class UiWebSocket |
146 | private void createHandlers() { | 147 | private void createHandlers() { |
147 | handlers = new HashMap<>(); | 148 | handlers = new HashMap<>(); |
148 | UiExtensionService service = directory.get(UiExtensionService.class); | 149 | UiExtensionService service = directory.get(UiExtensionService.class); |
149 | - service.getExtensions().forEach(ext -> ext.messageHandlerFactory().newHandlers().forEach(handler -> { | 150 | + service.getExtensions().forEach(ext -> { |
150 | - handler.init(this, directory); | 151 | + UiMessageHandlerFactory factory = ext.messageHandlerFactory(); |
151 | - handler.messageTypes().forEach(type -> handlers.put(type, handler)); | 152 | + if (factory != null) { |
152 | - })); | 153 | + factory.newHandlers().forEach(handler -> { |
154 | + handler.init(this, directory); | ||
155 | + handler.messageTypes().forEach(type -> handlers.put(type, handler)); | ||
156 | + }); | ||
157 | + } | ||
158 | + }); | ||
153 | } | 159 | } |
154 | 160 | ||
155 | // Destroys message handlers. | 161 | // Destroys message handlers. | ... | ... |
... | @@ -151,12 +151,24 @@ | ... | @@ -151,12 +151,24 @@ |
151 | 151 | ||
152 | <servlet> | 152 | <servlet> |
153 | <servlet-name>Web Socket Service</servlet-name> | 153 | <servlet-name>Web Socket Service</servlet-name> |
154 | - <servlet-class>org.onosproject.ui.impl.GuiWebSocketServlet</servlet-class> | 154 | + <servlet-class>org.onosproject.ui.impl.UiWebSocketServlet</servlet-class> |
155 | <load-on-startup>2</load-on-startup> | 155 | <load-on-startup>2</load-on-startup> |
156 | </servlet> | 156 | </servlet> |
157 | 157 | ||
158 | <servlet-mapping> | 158 | <servlet-mapping> |
159 | <servlet-name>Web Socket Service</servlet-name> | 159 | <servlet-name>Web Socket Service</servlet-name> |
160 | + <url-pattern>/websock/*</url-pattern> | ||
161 | + </servlet-mapping> | ||
162 | + | ||
163 | + | ||
164 | + <servlet> | ||
165 | + <servlet-name>Legacy Web Socket Service</servlet-name> | ||
166 | + <servlet-class>org.onosproject.ui.impl.GuiWebSocketServlet</servlet-class> | ||
167 | + <load-on-startup>2</load-on-startup> | ||
168 | + </servlet> | ||
169 | + | ||
170 | + <servlet-mapping> | ||
171 | + <servlet-name>Legacy Web Socket Service</servlet-name> | ||
160 | <url-pattern>/ws/*</url-pattern> | 172 | <url-pattern>/ws/*</url-pattern> |
161 | </servlet-mapping> | 173 | </servlet-mapping> |
162 | 174 | ... | ... |
... | @@ -22,7 +22,7 @@ | ... | @@ -22,7 +22,7 @@ |
22 | 22 | ||
23 | var uiContext = '/onos/ui/', | 23 | var uiContext = '/onos/ui/', |
24 | rsSuffix = uiContext + 'rs/', | 24 | rsSuffix = uiContext + 'rs/', |
25 | - wsSuffix = uiContext + 'ws/'; | 25 | + wsSuffix = uiContext + 'websock/'; |
26 | 26 | ||
27 | angular.module('onosRemote') | 27 | angular.module('onosRemote') |
28 | .factory('UrlFnService', ['$location', function ($loc) { | 28 | .factory('UrlFnService', ['$location', function ($loc) { | ... | ... |
... | @@ -20,62 +20,105 @@ | ... | @@ -20,62 +20,105 @@ |
20 | (function () { | 20 | (function () { |
21 | 'use strict'; | 21 | 'use strict'; |
22 | 22 | ||
23 | - var fs; | 23 | + // injected refs |
24 | + var fs, $log; | ||
24 | 25 | ||
25 | - function fnOpen(f) { | 26 | + // internal state |
26 | - // wrap the onOpen function; we will handle any housekeeping here... | 27 | + var ws, sws, sid = 0, |
27 | - if (!fs.isF(f)) { | 28 | + handlers = {}; |
28 | - return null; | 29 | + |
30 | + function resetSid() { | ||
31 | + sid = 0; | ||
32 | + } | ||
33 | + | ||
34 | + // Binds the specified message handlers. | ||
35 | + function bindHandlers(handlerMap) { | ||
36 | + var m = d3.map(handlerMap), | ||
37 | + dups = []; | ||
38 | + | ||
39 | + m.forEach(function (key, value) { | ||
40 | + var fn = fs.isF(value[key]); | ||
41 | + if (!fn) { | ||
42 | + $log.warn(key + ' binding not a function on ' + value); | ||
43 | + return; | ||
44 | + } | ||
45 | + | ||
46 | + if (handlers[key]) { | ||
47 | + dups.push(key); | ||
48 | + } else { | ||
49 | + handlers[key] = fn; | ||
50 | + } | ||
51 | + }); | ||
52 | + if (dups.length) { | ||
53 | + $log.warn('duplicate bindings ignored:', dups); | ||
29 | } | 54 | } |
55 | + } | ||
30 | 56 | ||
31 | - return function (openEvent) { | 57 | + // Unbinds the specified message handlers. |
32 | - // NOTE: nothing worth passing to the caller? | 58 | + function unbindHandlers(handlerMap) { |
33 | - f(); | 59 | + var m = d3.map(handlerMap); |
34 | - }; | 60 | + m.forEach(function (key) { |
61 | + delete handlers[key]; | ||
62 | + }); | ||
35 | } | 63 | } |
36 | 64 | ||
37 | - function fnMessage(f) { | 65 | + // Formulates an event message and sends it via the shared web-socket. |
38 | - // wrap the onMessage function; we will attempt to decode the | 66 | + function sendEvent(evType, payload) { |
39 | - // message event payload as JSON and pass that in... | 67 | + var p = payload || {}; |
40 | - if (!fs.isF(f)) { | 68 | + if (sws) { |
41 | - return null; | 69 | + $log.debug(' *Tx* >> ', evType, payload); |
70 | + sws.send({ | ||
71 | + event: evType, | ||
72 | + sid: ++sid, | ||
73 | + payload: p | ||
74 | + }); | ||
75 | + } else { | ||
76 | + $log.warn('sendEvent: no websocket open:', evType, payload); | ||
42 | } | 77 | } |
78 | + } | ||
43 | 79 | ||
44 | - return function (msgEvent) { | 80 | + |
45 | - var ev; | 81 | + // Handles the specified message using handler bindings. |
46 | - try { | 82 | + function handleMessage(msgEvent) { |
47 | - ev = JSON.parse(msgEvent.data); | 83 | + var ev; |
48 | - } catch (e) { | 84 | + try { |
49 | - ev = { | 85 | + ev = JSON.parse(msgEvent.data); |
50 | - error: 'Failed to parse JSON', | 86 | + $log.debug(' *Rx* >> ', ev.event, ev.payload); |
51 | - e: e | 87 | + dispatchToHandler(ev); |
52 | - }; | 88 | + } catch (e) { |
53 | - } | 89 | + $log.error('message is not valid JSON', msgEvent); |
54 | - f(ev); | 90 | + } |
55 | - }; | ||
56 | } | 91 | } |
57 | 92 | ||
58 | - function fnClose(f) { | 93 | + // Dispatches the message to the appropriate handler. |
59 | - // wrap the onClose function; we will handle any parameters to the | 94 | + function dispatchToHandler(event) { |
60 | - // close event here... | 95 | + var handler = handlers[event.event]; |
61 | - if (!fs.isF(f)) { | 96 | + if (handler) { |
62 | - return null; | 97 | + handler(event.payload); |
98 | + } else { | ||
99 | + $log.warn('unhandled event:', event); | ||
63 | } | 100 | } |
101 | + } | ||
102 | + | ||
103 | + function handleOpen() { | ||
104 | + $log.info('web socket open'); | ||
105 | + // FIXME: implement calling external hooks | ||
106 | + } | ||
64 | 107 | ||
65 | - return function (closeEvent) { | 108 | + function handleClose() { |
66 | - // NOTE: only seen {reason == ""} so far, nevertheless... | 109 | + $log.info('web socket closed'); |
67 | - f(closeEvent.reason); | 110 | + // FIXME: implement reconnect logic |
68 | - }; | ||
69 | } | 111 | } |
70 | 112 | ||
71 | angular.module('onosRemote') | 113 | angular.module('onosRemote') |
72 | .factory('WebSocketService', | 114 | .factory('WebSocketService', |
73 | ['$log', '$location', 'UrlFnService', 'FnService', | 115 | ['$log', '$location', 'UrlFnService', 'FnService', |
74 | 116 | ||
75 | - function ($log, $loc, ufs, _fs_) { | 117 | + function (_$log_, $loc, ufs, _fs_) { |
76 | fs = _fs_; | 118 | fs = _fs_; |
119 | + $log = _$log_; | ||
77 | 120 | ||
78 | - // creates a web socket for the given path, returning a "handle". | 121 | + // Creates a web socket for the given path, returning a "handle". |
79 | // opts contains the event handler callbacks, etc. | 122 | // opts contains the event handler callbacks, etc. |
80 | function createWebSocket(path, opts) { | 123 | function createWebSocket(path, opts) { |
81 | var o = opts || {}, | 124 | var o = opts || {}, |
... | @@ -85,8 +128,7 @@ | ... | @@ -85,8 +128,7 @@ |
85 | meta: { path: fullUrl, ws: null }, | 128 | meta: { path: fullUrl, ws: null }, |
86 | send: send, | 129 | send: send, |
87 | close: close | 130 | close: close |
88 | - }, | 131 | + }; |
89 | - ws; | ||
90 | 132 | ||
91 | try { | 133 | try { |
92 | ws = new WebSocket(fullUrl); | 134 | ws = new WebSocket(fullUrl); |
... | @@ -97,23 +139,21 @@ | ... | @@ -97,23 +139,21 @@ |
97 | $log.debug('Attempting to open websocket to: ' + fullUrl); | 139 | $log.debug('Attempting to open websocket to: ' + fullUrl); |
98 | 140 | ||
99 | if (ws) { | 141 | if (ws) { |
100 | - ws.onopen = fnOpen(o.onOpen); | 142 | + ws.onopen = handleOpen; |
101 | - ws.onmessage = fnMessage(o.onMessage); | 143 | + ws.onmessage = handleMessage; |
102 | - ws.onclose = fnClose(o.onClose); | 144 | + ws.onclose = handleClose; |
103 | } | 145 | } |
104 | 146 | ||
105 | - // messages are expected to be event objects.. | 147 | + // Sends a formulated event message via the backing web-socket. |
106 | function send(ev) { | 148 | function send(ev) { |
107 | - if (ev) { | 149 | + if (ev && ws) { |
108 | - if (ws) { | 150 | + ws.send(JSON.stringify(ev)); |
109 | - ws.send(JSON.stringify(ev)); | 151 | + } else if (!ws) { |
110 | - } else { | 152 | + $log.warn('ws.send() no web socket open!', fullUrl, ev); |
111 | - $log.warn('ws.send() no web socket open!', | ||
112 | - fullUrl, ev); | ||
113 | - } | ||
114 | } | 153 | } |
115 | } | 154 | } |
116 | 155 | ||
156 | + // Closes the backing web-socket. | ||
117 | function close() { | 157 | function close() { |
118 | if (ws) { | 158 | if (ws) { |
119 | ws.close(); | 159 | ws.close(); |
... | @@ -122,11 +162,16 @@ | ... | @@ -122,11 +162,16 @@ |
122 | } | 162 | } |
123 | } | 163 | } |
124 | 164 | ||
165 | + sws = api; // Make the shared web-socket accessible | ||
125 | return api; | 166 | return api; |
126 | } | 167 | } |
127 | 168 | ||
128 | return { | 169 | return { |
129 | - createWebSocket: createWebSocket | 170 | + resetSid: resetSid, |
171 | + createWebSocket: createWebSocket, | ||
172 | + bindHandlers: bindHandlers, | ||
173 | + unbindHandlers: unbindHandlers, | ||
174 | + sendEvent: sendEvent | ||
130 | }; | 175 | }; |
131 | }]); | 176 | }]); |
132 | 177 | ... | ... |
... | @@ -263,7 +263,7 @@ | ... | @@ -263,7 +263,7 @@ |
263 | // Cleanup on destroyed scope.. | 263 | // Cleanup on destroyed scope.. |
264 | $scope.$on('$destroy', function () { | 264 | $scope.$on('$destroy', function () { |
265 | $log.log('OvTopoCtrl is saying Buh-Bye!'); | 265 | $log.log('OvTopoCtrl is saying Buh-Bye!'); |
266 | - tes.closeSock(); | 266 | + tes.stop(); |
267 | tps.destroyPanels(); | 267 | tps.destroyPanels(); |
268 | tis.destroyInst(); | 268 | tis.destroyInst(); |
269 | tfs.destroyForce(); | 269 | tfs.destroyForce(); |
... | @@ -291,7 +291,7 @@ | ... | @@ -291,7 +291,7 @@ |
291 | tfs.initForce(svg, forceG, uplink, dim); | 291 | tfs.initForce(svg, forceG, uplink, dim); |
292 | tis.initInst({ showMastership: tfs.showMastership }); | 292 | tis.initInst({ showMastership: tfs.showMastership }); |
293 | tps.initPanels({ sendEvent: tes.sendEvent }); | 293 | tps.initPanels({ sendEvent: tes.sendEvent }); |
294 | - tes.openSock(); | 294 | + tes.start(); |
295 | 295 | ||
296 | $log.log('OvTopoCtrl has been created'); | 296 | $log.log('OvTopoCtrl has been created'); |
297 | }]); | 297 | }]); | ... | ... |
... | @@ -27,15 +27,15 @@ | ... | @@ -27,15 +27,15 @@ |
27 | 'use strict'; | 27 | 'use strict'; |
28 | 28 | ||
29 | // injected refs | 29 | // injected refs |
30 | - var $log, wss, wes, vs, tps, tis, tfs, tss, tts; | 30 | + var $log, vs, wss, tps, tis, tfs, tss, tts; |
31 | 31 | ||
32 | // internal state | 32 | // internal state |
33 | - var wsock, evApis; | 33 | + var handlers; |
34 | 34 | ||
35 | // ========================== | 35 | // ========================== |
36 | 36 | ||
37 | - function bindApis() { | 37 | + function createHandlers() { |
38 | - evApis = { | 38 | + handlers = { |
39 | showSummary: tps, | 39 | showSummary: tps, |
40 | 40 | ||
41 | showDetails: tss, | 41 | showDetails: tss, |
... | @@ -58,103 +58,43 @@ | ... | @@ -58,103 +58,43 @@ |
58 | }; | 58 | }; |
59 | } | 59 | } |
60 | 60 | ||
61 | - var nilApi = {}, | 61 | + var nilApi = {}; |
62 | - dispatcher = { | ||
63 | - handleEvent: function (ev) { | ||
64 | - var eid = ev.event, | ||
65 | - api = evApis[eid] || nilApi, | ||
66 | - eh = api[eid]; | ||
67 | - | ||
68 | - if (eh) { | ||
69 | - $log.debug(' << *Rx* ', eid, ev.payload); | ||
70 | - eh(ev.payload); | ||
71 | - } else { | ||
72 | - $log.warn('Unknown event (ignored):', ev); | ||
73 | - } | ||
74 | - }, | ||
75 | - | ||
76 | - sendEvent: function (evType, payload) { | ||
77 | - if (wsock) { | ||
78 | - $log.debug(' *Tx* >> ', evType, payload); | ||
79 | - wes.sendEvent(wsock, evType, payload); | ||
80 | - } else { | ||
81 | - $log.warn('sendEvent: no websocket open:', evType, payload); | ||
82 | - } | ||
83 | - } | ||
84 | - }; | ||
85 | - | ||
86 | - // === Web Socket functions === | ||
87 | - | ||
88 | - function onWsOpen() { | ||
89 | - $log.debug('web socket opened...'); | ||
90 | - // start by requesting periodic summary data... | ||
91 | - dispatcher.sendEvent('requestSummary'); | ||
92 | - vs.hide(); | ||
93 | - } | ||
94 | - | ||
95 | - function onWsMessage(ev) { | ||
96 | - dispatcher.handleEvent(ev); | ||
97 | - } | ||
98 | - | ||
99 | - function onWsClose(reason) { | ||
100 | - $log.log('web socket closed; reason=', reason); | ||
101 | - wsock = null; | ||
102 | - vs.lostServer('OvTopoCtrl', [ | ||
103 | - 'Oops!', | ||
104 | - 'Web-socket connection to server closed...', | ||
105 | - 'Try refreshing the page.' | ||
106 | - ]); | ||
107 | - } | ||
108 | - | ||
109 | - // ========================== | ||
110 | 62 | ||
111 | angular.module('ovTopo') | 63 | angular.module('ovTopo') |
112 | .factory('TopoEventService', | 64 | .factory('TopoEventService', |
113 | - ['$log', '$location', 'WebSocketService', 'WsEventService', 'VeilService', | 65 | + ['$log', '$location', 'VeilService', 'WebSocketService', |
114 | 'TopoPanelService', 'TopoInstService', 'TopoForceService', | 66 | 'TopoPanelService', 'TopoInstService', 'TopoForceService', |
115 | 'TopoSelectService', 'TopoTrafficService', | 67 | 'TopoSelectService', 'TopoTrafficService', |
116 | 68 | ||
117 | - function (_$log_, $loc, _wss_, _wes_, _vs_, | 69 | + function (_$log_, $loc, _vs_, _wss_, _tps_, _tis_, _tfs_, _tss_, _tts_) { |
118 | - _tps_, _tis_, _tfs_, _tss_, _tts_) { | ||
119 | $log = _$log_; | 70 | $log = _$log_; |
120 | - wss = _wss_; | ||
121 | - wes = _wes_; | ||
122 | vs = _vs_; | 71 | vs = _vs_; |
72 | + wss = _wss_; | ||
123 | tps = _tps_; | 73 | tps = _tps_; |
124 | tis = _tis_; | 74 | tis = _tis_; |
125 | tfs = _tfs_; | 75 | tfs = _tfs_; |
126 | tss = _tss_; | 76 | tss = _tss_; |
127 | tts = _tts_; | 77 | tts = _tts_; |
128 | 78 | ||
129 | - bindApis(); | 79 | + createHandlers(); |
130 | - | ||
131 | - // TODO: handle "guiSuccessor" functionality (replace host) | ||
132 | - // TODO: implement retry on close functionality | ||
133 | 80 | ||
134 | - function openSock() { | 81 | + // FIXME: need to handle async socket open to avoid race |
135 | - wsock = wss.createWebSocket('topology', { | 82 | + function start() { |
136 | - onOpen: onWsOpen, | 83 | + wss.bindHandlers(handlers); |
137 | - onMessage: onWsMessage, | 84 | + wss.sendEvent('topoStart'); |
138 | - onClose: onWsClose, | 85 | + $log.debug('topo comms started'); |
139 | - wsport: $loc.search().wsport | ||
140 | - }); | ||
141 | - $log.debug('web socket opened:', wsock); | ||
142 | } | 86 | } |
143 | 87 | ||
144 | - function closeSock() { | 88 | + function stop() { |
145 | - var path; | 89 | + wss.unbindHandlers(); |
146 | - if (wsock) { | 90 | + wss.sendEvent('topoStop'); |
147 | - path = wsock.meta.path; | 91 | + $log.debug('topo comms stopped'); |
148 | - wsock.close(); | ||
149 | - wsock = null; | ||
150 | - $log.debug('web socket closed. path:', path); | ||
151 | - } | ||
152 | } | 92 | } |
153 | 93 | ||
154 | return { | 94 | return { |
155 | - openSock: openSock, | 95 | + start: start, |
156 | - closeSock: closeSock, | 96 | + stop: stop, |
157 | - sendEvent: dispatcher.sendEvent | 97 | + sendEvent: wss.sendEvent |
158 | }; | 98 | }; |
159 | }]); | 99 | }]); |
160 | }()); | 100 | }()); | ... | ... |
... | @@ -64,10 +64,10 @@ | ... | @@ -64,10 +64,10 @@ |
64 | .controller('OnosCtrl', [ | 64 | .controller('OnosCtrl', [ |
65 | '$log', '$route', '$routeParams', '$location', | 65 | '$log', '$route', '$routeParams', '$location', |
66 | 'KeyService', 'ThemeService', 'GlyphService', 'PanelService', | 66 | 'KeyService', 'ThemeService', 'GlyphService', 'PanelService', |
67 | - 'FlashService', 'QuickHelpService', | 67 | + 'FlashService', 'QuickHelpService', 'WebSocketService', |
68 | 68 | ||
69 | function ($log, $route, $routeParams, $location, | 69 | function ($log, $route, $routeParams, $location, |
70 | - ks, ts, gs, ps, flash, qhs) { | 70 | + ks, ts, gs, ps, flash, qhs, wss) { |
71 | var self = this; | 71 | var self = this; |
72 | 72 | ||
73 | self.$route = $route; | 73 | self.$route = $route; |
... | @@ -84,6 +84,13 @@ | ... | @@ -84,6 +84,13 @@ |
84 | flash.initFlash(); | 84 | flash.initFlash(); |
85 | qhs.initQuickHelp(); | 85 | qhs.initQuickHelp(); |
86 | 86 | ||
87 | + // TODO: register handlers for initial messages: instances, settings, etc. | ||
88 | + | ||
89 | + // TODO: opts? | ||
90 | + wss.createWebSocket('core', { | ||
91 | + wsport: $location.search().wsport | ||
92 | + }); | ||
93 | + | ||
87 | $log.log('OnosCtrl has been created'); | 94 | $log.log('OnosCtrl has been created'); |
88 | 95 | ||
89 | $log.debug('route: ', self.$route); | 96 | $log.debug('route: ', self.$route); | ... | ... |
-
Please register or login to post a comment