Thomas Vachuska

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
...@@ -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
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
15 */ 15 */
16 16
17 /* 17 /*
18 + DEPRECATED: to be deleted
18 ONOS GUI -- Remote -- Web Socket Event Service 19 ONOS GUI -- Remote -- Web Socket Event Service
19 */ 20 */
20 (function () { 21 (function () {
......
...@@ -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);
......