blob: e0a91641cc08e70ff672488086cbcc7a94675459 [file] [log] [blame]
Thomas Vachuska329af532015-03-10 02:08:33 -07001/*
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 */
16package org.onosproject.ui.impl;
17
18import com.fasterxml.jackson.databind.JsonNode;
19import com.fasterxml.jackson.databind.ObjectMapper;
20import com.fasterxml.jackson.databind.node.ArrayNode;
21import com.fasterxml.jackson.databind.node.ObjectNode;
22import org.onlab.osgi.ServiceDirectory;
23import org.onlab.packet.IpAddress;
24import org.onosproject.cluster.ClusterEvent;
25import org.onosproject.cluster.ClusterService;
26import org.onosproject.cluster.ControllerNode;
27import org.onosproject.cluster.NodeId;
28import org.onosproject.core.CoreService;
29import org.onosproject.mastership.MastershipService;
30import org.onosproject.net.Annotated;
31import org.onosproject.net.AnnotationKeys;
32import org.onosproject.net.Annotations;
33import org.onosproject.net.ConnectPoint;
34import org.onosproject.net.DefaultEdgeLink;
35import org.onosproject.net.Device;
36import org.onosproject.net.DeviceId;
37import org.onosproject.net.EdgeLink;
38import org.onosproject.net.Host;
39import org.onosproject.net.HostId;
40import org.onosproject.net.HostLocation;
41import org.onosproject.net.Link;
42import org.onosproject.net.LinkKey;
43import org.onosproject.net.PortNumber;
44import org.onosproject.net.device.DeviceEvent;
45import org.onosproject.net.device.DeviceService;
46import org.onosproject.net.flow.FlowEntry;
47import org.onosproject.net.flow.FlowRuleService;
48import org.onosproject.net.flow.TrafficTreatment;
49import org.onosproject.net.flow.instructions.Instruction;
50import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
51import org.onosproject.net.host.HostEvent;
52import org.onosproject.net.host.HostService;
53import org.onosproject.net.intent.Intent;
54import org.onosproject.net.intent.IntentService;
55import org.onosproject.net.intent.LinkCollectionIntent;
56import org.onosproject.net.intent.OpticalConnectivityIntent;
57import org.onosproject.net.intent.OpticalPathIntent;
58import org.onosproject.net.intent.PathIntent;
59import org.onosproject.net.link.LinkEvent;
60import org.onosproject.net.link.LinkService;
61import org.onosproject.net.provider.ProviderId;
62import org.onosproject.net.statistic.Load;
63import org.onosproject.net.statistic.StatisticService;
64import org.onosproject.net.topology.Topology;
65import org.onosproject.net.topology.TopologyService;
Simon Huntd2747a02015-04-30 22:41:16 -070066import org.onosproject.ui.JsonUtils;
Thomas Vachuska329af532015-03-10 02:08:33 -070067import org.onosproject.ui.UiConnection;
Simon Huntd2747a02015-04-30 22:41:16 -070068import org.onosproject.ui.UiMessageHandlerTwo;
Thomas Vachuska329af532015-03-10 02:08:33 -070069import org.slf4j.Logger;
70import org.slf4j.LoggerFactory;
71
72import java.text.DecimalFormat;
73import java.util.ArrayList;
74import java.util.Collection;
75import java.util.Collections;
76import java.util.HashMap;
77import java.util.HashSet;
78import java.util.Iterator;
79import java.util.List;
80import java.util.Map;
81import java.util.Set;
82import java.util.concurrent.ConcurrentHashMap;
83
84import static com.google.common.base.Preconditions.checkNotNull;
85import static com.google.common.base.Strings.isNullOrEmpty;
86import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
87import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
88import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
89import static org.onosproject.net.DeviceId.deviceId;
90import static org.onosproject.net.HostId.hostId;
91import static org.onosproject.net.LinkKey.linkKey;
92import static org.onosproject.net.PortNumber.P0;
93import static org.onosproject.net.PortNumber.portNumber;
94import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
95import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
96import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
97import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
98import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
99import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
100
101/**
102 * Facility for creating messages bound for the topology viewer.
103 */
Simon Huntd2747a02015-04-30 22:41:16 -0700104public abstract class TopologyViewMessageHandlerBase extends UiMessageHandlerTwo {
Thomas Vachuska329af532015-03-10 02:08:33 -0700105
Simon Huntd2747a02015-04-30 22:41:16 -0700106 protected static final Logger log =
107 LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700108
Simon Huntd2747a02015-04-30 22:41:16 -0700109 private static final ProviderId PID =
110 new ProviderId("core", "org.onosproject.core", true);
Thomas Vachuska329af532015-03-10 02:08:33 -0700111 private static final String COMPACT = "%s/%s-%s/%s";
112
113 private static final double KB = 1024;
114 private static final double MB = 1024 * KB;
115 private static final double GB = 1024 * MB;
116
117 private static final String GB_UNIT = "GB";
118 private static final String MB_UNIT = "MB";
119 private static final String KB_UNIT = "KB";
120 private static final String B_UNIT = "B";
121
122 protected ServiceDirectory directory;
123 protected ClusterService clusterService;
124 protected DeviceService deviceService;
125 protected LinkService linkService;
126 protected HostService hostService;
127 protected MastershipService mastershipService;
128 protected IntentService intentService;
129 protected FlowRuleService flowService;
130 protected StatisticService statService;
131 protected TopologyService topologyService;
132
Thomas Vachuska329af532015-03-10 02:08:33 -0700133 private String version;
134
135 // TODO: extract into an external & durable state; good enough for now and demo
136 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
137
138 /**
Thomas Vachuska329af532015-03-10 02:08:33 -0700139 * Returns read-only view of the meta-ui information.
140 *
141 * @return map of id to meta-ui mementos
142 */
143 static Map<String, ObjectNode> getMetaUi() {
144 return Collections.unmodifiableMap(metaUi);
145 }
146
147 @Override
148 public void init(UiConnection connection, ServiceDirectory directory) {
149 super.init(connection, directory);
150 this.directory = checkNotNull(directory, "Directory cannot be null");
151 clusterService = directory.get(ClusterService.class);
152 deviceService = directory.get(DeviceService.class);
153 linkService = directory.get(LinkService.class);
154 hostService = directory.get(HostService.class);
155 mastershipService = directory.get(MastershipService.class);
156 intentService = directory.get(IntentService.class);
157 flowService = directory.get(FlowRuleService.class);
158 statService = directory.get(StatisticService.class);
159 topologyService = directory.get(TopologyService.class);
160
161 String ver = directory.get(CoreService.class).version().toString();
162 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
163 }
164
Thomas Vachuska329af532015-03-10 02:08:33 -0700165 // Returns the specified set of IP addresses as a string.
166 private String ip(Set<IpAddress> ipAddresses) {
167 Iterator<IpAddress> it = ipAddresses.iterator();
168 return it.hasNext() ? it.next().toString() : "unknown";
169 }
170
171 // Produces JSON structure from annotations.
172 private JsonNode props(Annotations annotations) {
173 ObjectNode props = mapper.createObjectNode();
174 if (annotations != null) {
175 for (String key : annotations.keys()) {
176 props.put(key, annotations.value(key));
177 }
178 }
179 return props;
180 }
181
182 // Produces an informational log message event bound to the client.
183 protected ObjectNode info(long id, String message) {
184 return message("info", id, message);
185 }
186
187 // Produces a warning log message event bound to the client.
188 protected ObjectNode warning(long id, String message) {
189 return message("warning", id, message);
190 }
191
192 // Produces an error log message event bound to the client.
193 protected ObjectNode error(long id, String message) {
194 return message("error", id, message);
195 }
196
197 // Produces a log message event bound to the client.
198 private ObjectNode message(String severity, long id, String message) {
Simon Huntd2747a02015-04-30 22:41:16 -0700199 ObjectNode payload = mapper.createObjectNode()
200 .put("severity", severity)
201 .put("message", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700202
Simon Huntd2747a02015-04-30 22:41:16 -0700203 return JsonUtils.envelope("message", id, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700204 }
205
206 // Produces a set of all hosts listed in the specified JSON array.
207 protected Set<Host> getHosts(ArrayNode array) {
208 Set<Host> hosts = new HashSet<>();
209 if (array != null) {
210 for (JsonNode node : array) {
211 try {
212 addHost(hosts, hostId(node.asText()));
213 } catch (IllegalArgumentException e) {
214 log.debug("Skipping ID {}", node.asText());
215 }
216 }
217 }
218 return hosts;
219 }
220
221 // Adds the specified host to the set of hosts.
222 private void addHost(Set<Host> hosts, HostId hostId) {
223 Host host = hostService.getHost(hostId);
224 if (host != null) {
225 hosts.add(host);
226 }
227 }
228
229
230 // Produces a set of all devices listed in the specified JSON array.
231 protected Set<Device> getDevices(ArrayNode array) {
232 Set<Device> devices = new HashSet<>();
233 if (array != null) {
234 for (JsonNode node : array) {
235 try {
236 addDevice(devices, deviceId(node.asText()));
237 } catch (IllegalArgumentException e) {
238 log.debug("Skipping ID {}", node.asText());
239 }
240 }
241 }
242 return devices;
243 }
244
245 private void addDevice(Set<Device> devices, DeviceId deviceId) {
246 Device device = deviceService.getDevice(deviceId);
247 if (device != null) {
248 devices.add(device);
249 }
250 }
251
252 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
253 try {
254 addHost(hosts, hostId(hover));
255 } catch (IllegalArgumentException e) {
256 try {
257 addDevice(devices, deviceId(hover));
258 } catch (IllegalArgumentException ne) {
259 log.debug("Skipping ID {}", hover);
260 }
261 }
262 }
263
264 // Produces a cluster instance message to the client.
265 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
266 ControllerNode node = event.subject();
267 int switchCount = mastershipService.getDevicesOf(node.id()).size();
268 ObjectNode payload = mapper.createObjectNode()
269 .put("id", node.id().toString())
270 .put("ip", node.ip().toString())
271 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700272 .put("uiAttached", node.equals(clusterService.getLocalNode()))
Thomas Vachuska329af532015-03-10 02:08:33 -0700273 .put("switches", switchCount);
274
275 ArrayNode labels = mapper.createArrayNode();
276 labels.add(node.id().toString());
277 labels.add(node.ip().toString());
278
279 // Add labels, props and stuff the payload into envelope.
280 payload.set("labels", labels);
281 addMetaUi(node.id().toString(), payload);
282
283 String type = messageType != null ? messageType :
284 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
285 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
286 "addInstance")));
Simon Huntd2747a02015-04-30 22:41:16 -0700287 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700288 }
289
290 // Produces a device event message to the client.
291 protected ObjectNode deviceMessage(DeviceEvent event) {
292 Device device = event.subject();
293 ObjectNode payload = mapper.createObjectNode()
294 .put("id", device.id().toString())
295 .put("type", device.type().toString().toLowerCase())
296 .put("online", deviceService.isAvailable(device.id()))
297 .put("master", master(device.id()));
298
299 // Generate labels: id, chassis id, no-label, optional-name
300 String name = device.annotations().value(AnnotationKeys.NAME);
301 ArrayNode labels = mapper.createArrayNode();
302 labels.add("");
303 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
304 labels.add(device.id().toString());
305
306 // Add labels, props and stuff the payload into envelope.
307 payload.set("labels", labels);
308 payload.set("props", props(device.annotations()));
309 addGeoLocation(device, payload);
310 addMetaUi(device.id().toString(), payload);
311
312 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
313 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
Simon Huntd2747a02015-04-30 22:41:16 -0700314 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700315 }
316
317 // Produces a link event message to the client.
318 protected ObjectNode linkMessage(LinkEvent event) {
319 Link link = event.subject();
320 ObjectNode payload = mapper.createObjectNode()
321 .put("id", compactLinkString(link))
322 .put("type", link.type().toString().toLowerCase())
323 .put("online", link.state() == Link.State.ACTIVE)
324 .put("linkWidth", 1.2)
325 .put("src", link.src().deviceId().toString())
326 .put("srcPort", link.src().port().toString())
327 .put("dst", link.dst().deviceId().toString())
328 .put("dstPort", link.dst().port().toString());
329 String type = (event.type() == LINK_ADDED) ? "addLink" :
330 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
Simon Huntd2747a02015-04-30 22:41:16 -0700331 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700332 }
333
334 // Produces a host event message to the client.
335 protected ObjectNode hostMessage(HostEvent event) {
336 Host host = event.subject();
337 String hostType = host.annotations().value(AnnotationKeys.TYPE);
338 ObjectNode payload = mapper.createObjectNode()
339 .put("id", host.id().toString())
340 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
341 .put("ingress", compactLinkString(edgeLink(host, true)))
342 .put("egress", compactLinkString(edgeLink(host, false)));
343 payload.set("cp", hostConnect(mapper, host.location()));
344 payload.set("labels", labels(mapper, ip(host.ipAddresses()),
345 host.mac().toString()));
346 payload.set("props", props(host.annotations()));
347 addGeoLocation(host, payload);
348 addMetaUi(host.id().toString(), payload);
349
350 String type = (event.type() == HOST_ADDED) ? "addHost" :
351 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
Simon Huntd2747a02015-04-30 22:41:16 -0700352 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700353 }
354
355 // Encodes the specified host location into a JSON object.
356 private ObjectNode hostConnect(ObjectMapper mapper, HostLocation location) {
357 return mapper.createObjectNode()
358 .put("device", location.deviceId().toString())
359 .put("port", location.port().toLong());
360 }
361
362 // Encodes the specified list of labels a JSON array.
363 private ArrayNode labels(ObjectMapper mapper, String... labels) {
364 ArrayNode json = mapper.createArrayNode();
365 for (String label : labels) {
366 json.add(label);
367 }
368 return json;
369 }
370
371 // Returns the name of the master node for the specified device id.
372 private String master(DeviceId deviceId) {
373 NodeId master = mastershipService.getMasterFor(deviceId);
374 return master != null ? master.toString() : "";
375 }
376
377 // Generates an edge link from the specified host location.
378 private EdgeLink edgeLink(Host host, boolean ingress) {
379 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
380 host.location(), ingress);
381 }
382
383 // Adds meta UI information for the specified object.
384 private void addMetaUi(String id, ObjectNode payload) {
385 ObjectNode meta = metaUi.get(id);
386 if (meta != null) {
387 payload.set("metaUi", meta);
388 }
389 }
390
391 // Adds a geo location JSON to the specified payload object.
392 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
393 Annotations annotations = annotated.annotations();
394 if (annotations == null) {
395 return;
396 }
397
398 String slat = annotations.value(AnnotationKeys.LATITUDE);
399 String slng = annotations.value(AnnotationKeys.LONGITUDE);
400 try {
401 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
402 double lat = Double.parseDouble(slat);
403 double lng = Double.parseDouble(slng);
404 ObjectNode loc = mapper.createObjectNode()
405 .put("type", "latlng").put("lat", lat).put("lng", lng);
406 payload.set("location", loc);
407 }
408 } catch (NumberFormatException e) {
409 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
410 }
411 }
412
413 // Updates meta UI information for the specified object.
Simon Huntd2747a02015-04-30 22:41:16 -0700414 protected void updateMetaUi(ObjectNode payload) {
415 metaUi.put(JsonUtils.string(payload, "id"),
416 JsonUtils.node(payload, "memento"));
Thomas Vachuska329af532015-03-10 02:08:33 -0700417 }
418
419 // Returns summary response.
420 protected ObjectNode summmaryMessage(long sid) {
421 Topology topology = topologyService.currentTopology();
Simon Huntd2747a02015-04-30 22:41:16 -0700422 return JsonUtils.envelope("showSummary", sid,
Thomas Vachuska329af532015-03-10 02:08:33 -0700423 json("ONOS Summary", "node",
424 new Prop("Devices", format(topology.deviceCount())),
425 new Prop("Links", format(topology.linkCount())),
426 new Prop("Hosts", format(hostService.getHostCount())),
427 new Prop("Topology SCCs", format(topology.clusterCount())),
428 new Separator(),
429 new Prop("Intents", format(intentService.getIntentCount())),
430 new Prop("Flows", format(flowService.getFlowRuleCount())),
431 new Prop("Version", version)));
432 }
433
434 // Returns device details response.
435 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
436 Device device = deviceService.getDevice(deviceId);
437 Annotations annot = device.annotations();
438 String name = annot.value(AnnotationKeys.NAME);
439 int portCount = deviceService.getPorts(deviceId).size();
440 int flowCount = getFlowCount(deviceId);
Simon Huntd2747a02015-04-30 22:41:16 -0700441 return JsonUtils.envelope("showDetails", sid,
Thomas Vachuska329af532015-03-10 02:08:33 -0700442 json(isNullOrEmpty(name) ? deviceId.toString() : name,
443 device.type().toString().toLowerCase(),
444 new Prop("URI", deviceId.toString()),
445 new Prop("Vendor", device.manufacturer()),
446 new Prop("H/W Version", device.hwVersion()),
447 new Prop("S/W Version", device.swVersion()),
448 new Prop("Serial Number", device.serialNumber()),
449 new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
450 new Separator(),
451 new Prop("Master", master(deviceId)),
452 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
453 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
454 new Separator(),
455 new Prop("Ports", Integer.toString(portCount)),
456 new Prop("Flows", Integer.toString(flowCount))));
457 }
458
459 protected int getFlowCount(DeviceId deviceId) {
460 int count = 0;
461 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
462 while (it.hasNext()) {
463 count++;
464 it.next();
465 }
466 return count;
467 }
468
469 // Counts all entries that egress on the given device links.
470 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
471 List<FlowEntry> entries = new ArrayList<>();
472 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
473 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
474 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
475 while (it.hasNext()) {
476 entries.add(it.next());
477 }
478
479 // Add all edge links to the set
480 if (hosts != null) {
481 for (Host host : hosts) {
482 links.add(new DefaultEdgeLink(host.providerId(),
483 new ConnectPoint(host.id(), P0),
484 host.location(), false));
485 }
486 }
487
488 Map<Link, Integer> counts = new HashMap<>();
489 for (Link link : links) {
490 counts.put(link, getEgressFlows(link, entries));
491 }
492 return counts;
493 }
494
495 // Counts all entries that egress on the link source port.
496 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
497 int count = 0;
498 PortNumber out = link.src().port();
499 for (FlowEntry entry : entries) {
500 TrafficTreatment treatment = entry.treatment();
Ray Milkey42507352015-03-20 15:16:10 -0700501 for (Instruction instruction : treatment.allInstructions()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700502 if (instruction.type() == Instruction.Type.OUTPUT &&
503 ((OutputInstruction) instruction).port().equals(out)) {
504 count++;
505 }
506 }
507 }
508 return count;
509 }
510
511
512 // Returns host details response.
513 protected ObjectNode hostDetails(HostId hostId, long sid) {
514 Host host = hostService.getHost(hostId);
515 Annotations annot = host.annotations();
516 String type = annot.value(AnnotationKeys.TYPE);
517 String name = annot.value(AnnotationKeys.NAME);
518 String vlan = host.vlan().toString();
Simon Huntd2747a02015-04-30 22:41:16 -0700519 return JsonUtils.envelope("showDetails", sid,
Thomas Vachuska329af532015-03-10 02:08:33 -0700520 json(isNullOrEmpty(name) ? hostId.toString() : name,
521 isNullOrEmpty(type) ? "endstation" : type,
522 new Prop("MAC", host.mac().toString()),
523 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
524 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
525 new Separator(),
526 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
527 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
528 }
529
530
531 // Produces JSON message to trigger traffic overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700532 protected ObjectNode trafficSummaryMessage() {
Thomas Vachuska329af532015-03-10 02:08:33 -0700533 ObjectNode payload = mapper.createObjectNode();
534 ArrayNode paths = mapper.createArrayNode();
535 payload.set("paths", paths);
536
537 ObjectNode pathNodeN = mapper.createObjectNode();
538 ArrayNode linksNodeN = mapper.createArrayNode();
539 ArrayNode labelsN = mapper.createArrayNode();
540
541 pathNodeN.put("class", "plain").put("traffic", false);
542 pathNodeN.set("links", linksNodeN);
543 pathNodeN.set("labels", labelsN);
544 paths.add(pathNodeN);
545
546 ObjectNode pathNodeT = mapper.createObjectNode();
547 ArrayNode linksNodeT = mapper.createArrayNode();
548 ArrayNode labelsT = mapper.createArrayNode();
549
550 pathNodeT.put("class", "secondary").put("traffic", true);
551 pathNodeT.set("links", linksNodeT);
552 pathNodeT.set("labels", labelsT);
553 paths.add(pathNodeT);
554
555 for (BiLink link : consolidateLinks(linkService.getLinks())) {
556 boolean bi = link.two != null;
557 if (isInfrastructureEgress(link.one) ||
558 (bi && isInfrastructureEgress(link.two))) {
559 link.addLoad(statService.load(link.one));
560 link.addLoad(bi ? statService.load(link.two) : null);
561 if (link.hasTraffic) {
562 linksNodeT.add(compactLinkString(link.one));
563 labelsT.add(formatBytes(link.bytes));
564 } else {
565 linksNodeN.add(compactLinkString(link.one));
566 labelsN.add("");
567 }
568 }
569 }
Simon Huntd2747a02015-04-30 22:41:16 -0700570 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700571 }
572
573 private Collection<BiLink> consolidateLinks(Iterable<Link> links) {
574 Map<LinkKey, BiLink> biLinks = new HashMap<>();
575 for (Link link : links) {
576 addLink(biLinks, link);
577 }
578 return biLinks.values();
579 }
580
581 // Produces JSON message to trigger flow overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700582 protected ObjectNode flowSummaryMessage(Set<Device> devices) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700583 ObjectNode payload = mapper.createObjectNode();
584 ArrayNode paths = mapper.createArrayNode();
585 payload.set("paths", paths);
586
587 for (Device device : devices) {
588 Map<Link, Integer> counts = getFlowCounts(device.id());
589 for (Link link : counts.keySet()) {
590 addLinkFlows(link, paths, counts.get(link));
591 }
592 }
Simon Huntd2747a02015-04-30 22:41:16 -0700593 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700594 }
595
596 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
597 ObjectNode pathNode = mapper.createObjectNode();
598 ArrayNode linksNode = mapper.createArrayNode();
599 ArrayNode labels = mapper.createArrayNode();
600 boolean noFlows = count == null || count == 0;
601 pathNode.put("class", noFlows ? "secondary" : "primary");
602 pathNode.put("traffic", false);
603 pathNode.set("links", linksNode.add(compactLinkString(link)));
604 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
605 (count == 1 ? " flow" : " flows"))));
606 paths.add(pathNode);
607 }
608
609
610 // Produces JSON message to trigger traffic visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700611 protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700612 ObjectNode payload = mapper.createObjectNode();
613 ArrayNode paths = mapper.createArrayNode();
614 payload.set("paths", paths);
615
616 // Classify links based on their traffic traffic first...
617 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
618
619 // Then separate the links into their respective classes and send them out.
620 Map<String, ObjectNode> pathNodes = new HashMap<>();
621 for (BiLink biLink : biLinks.values()) {
622 boolean hasTraffic = biLink.hasTraffic;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700623 String tc = (biLink.classes() + (hasTraffic ? " animated" : "")).trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700624 ObjectNode pathNode = pathNodes.get(tc);
625 if (pathNode == null) {
626 pathNode = mapper.createObjectNode()
627 .put("class", tc).put("traffic", hasTraffic);
628 pathNode.set("links", mapper.createArrayNode());
629 pathNode.set("labels", mapper.createArrayNode());
630 pathNodes.put(tc, pathNode);
631 paths.add(pathNode);
632 }
633 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
634 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
635 }
636
Simon Huntd2747a02015-04-30 22:41:16 -0700637 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700638 }
639
640 // Classifies the link traffic according to the specified classes.
641 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
642 Map<LinkKey, BiLink> biLinks = new HashMap<>();
643 for (TrafficClass trafficClass : trafficClasses) {
644 for (Intent intent : trafficClass.intents) {
645 boolean isOptical = intent instanceof OpticalConnectivityIntent;
646 List<Intent> installables = intentService.getInstallableIntents(intent.key());
647 if (installables != null) {
648 for (Intent installable : installables) {
649 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
650 if (installable instanceof PathIntent) {
651 classifyLinks(type, biLinks, trafficClass.showTraffic,
652 ((PathIntent) installable).path().links());
653 } else if (installable instanceof LinkCollectionIntent) {
654 classifyLinks(type, biLinks, trafficClass.showTraffic,
655 ((LinkCollectionIntent) installable).links());
656 } else if (installable instanceof OpticalPathIntent) {
657 classifyLinks(type, biLinks, trafficClass.showTraffic,
658 ((OpticalPathIntent) installable).path().links());
659 }
660 }
661 }
662 }
663 }
664 return biLinks;
665 }
666
667
668 // Adds the link segments (path or tree) associated with the specified
669 // connectivity intent
670 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
671 boolean showTraffic, Iterable<Link> links) {
672 if (links != null) {
673 for (Link link : links) {
674 BiLink biLink = addLink(biLinks, link);
675 if (isInfrastructureEgress(link)) {
676 if (showTraffic) {
677 biLink.addLoad(statService.load(link));
678 }
679 biLink.addClass(type);
680 }
681 }
682 }
683 }
684
685
Thomas Vachuska583bc632015-04-14 10:10:57 -0700686 static BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700687 LinkKey key = canonicalLinkKey(link);
688 BiLink biLink = biLinks.get(key);
689 if (biLink != null) {
690 biLink.setOther(link);
691 } else {
692 biLink = new BiLink(key, link);
693 biLinks.put(key, biLink);
694 }
695 return biLink;
696 }
697
698
699 // Adds the link segments (path or tree) associated with the specified
700 // connectivity intent
701 protected void addPathTraffic(ArrayNode paths, String type, String trafficType,
702 Iterable<Link> links) {
703 ObjectNode pathNode = mapper.createObjectNode();
704 ArrayNode linksNode = mapper.createArrayNode();
705
706 if (links != null) {
707 ArrayNode labels = mapper.createArrayNode();
708 boolean hasTraffic = false;
709 for (Link link : links) {
710 if (isInfrastructureEgress(link)) {
711 linksNode.add(compactLinkString(link));
712 Load load = statService.load(link);
713 String label = "";
714 if (load.rate() > 0) {
715 hasTraffic = true;
716 label = formatBytes(load.latest());
717 }
718 labels.add(label);
719 }
720 }
721 pathNode.put("class", hasTraffic ? type + " " + trafficType : type);
722 pathNode.put("traffic", hasTraffic);
723 pathNode.set("links", linksNode);
724 pathNode.set("labels", labels);
725 paths.add(pathNode);
726 }
727 }
728
729 // Poor-mans formatting to get the labels with byte counts looking nice.
730 private String formatBytes(long bytes) {
731 String unit;
732 double value;
733 if (bytes > GB) {
734 value = bytes / GB;
735 unit = GB_UNIT;
736 } else if (bytes > MB) {
737 value = bytes / MB;
738 unit = MB_UNIT;
739 } else if (bytes > KB) {
740 value = bytes / KB;
741 unit = KB_UNIT;
742 } else {
743 value = bytes;
744 unit = B_UNIT;
745 }
746 DecimalFormat format = new DecimalFormat("#,###.##");
747 return format.format(value) + " " + unit;
748 }
749
750 // Formats the given number into a string.
751 private String format(Number number) {
752 DecimalFormat format = new DecimalFormat("#,###");
753 return format.format(number);
754 }
755
756 private boolean isInfrastructureEgress(Link link) {
757 return link.src().elementId() instanceof DeviceId;
758 }
759
760 // Produces compact string representation of a link.
761 private static String compactLinkString(Link link) {
762 return String.format(COMPACT, link.src().elementId(), link.src().port(),
763 link.dst().elementId(), link.dst().port());
764 }
765
766 // Produces JSON property details.
767 private ObjectNode json(String id, String type, Prop... props) {
768 ObjectMapper mapper = new ObjectMapper();
769 ObjectNode result = mapper.createObjectNode()
770 .put("id", id).put("type", type);
771 ObjectNode pnode = mapper.createObjectNode();
772 ArrayNode porder = mapper.createArrayNode();
773 for (Prop p : props) {
774 porder.add(p.key);
775 pnode.put(p.key, p.value);
776 }
777 result.set("propOrder", porder);
778 result.set("props", pnode);
779 return result;
780 }
781
782 // Produces canonical link key, i.e. one that will match link and its inverse.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700783 static LinkKey canonicalLinkKey(Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700784 String sn = link.src().elementId().toString();
785 String dn = link.dst().elementId().toString();
786 return sn.compareTo(dn) < 0 ?
787 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
788 }
789
790 // Representation of link and its inverse and any traffic data.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700791 static class BiLink {
Thomas Vachuska329af532015-03-10 02:08:33 -0700792 public final LinkKey key;
793 public final Link one;
794 public Link two;
795 public boolean hasTraffic = false;
796 public long bytes = 0;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700797
798 private Set<String> classes = new HashSet<>();
Thomas Vachuska329af532015-03-10 02:08:33 -0700799
800 BiLink(LinkKey key, Link link) {
801 this.key = key;
802 this.one = link;
803 }
804
805 void setOther(Link link) {
806 this.two = link;
807 }
808
809 void addLoad(Load load) {
810 if (load != null) {
811 this.hasTraffic = hasTraffic || load.rate() > 0;
812 this.bytes += load.latest();
813 }
814 }
815
816 void addClass(String trafficClass) {
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700817 classes.add(trafficClass);
818 }
819
820 String classes() {
821 StringBuilder sb = new StringBuilder();
822 classes.forEach(c -> sb.append(c).append(" "));
823 return sb.toString().trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700824 }
825 }
826
827 // Auxiliary key/value carrier.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700828 static class Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700829 public final String key;
830 public final String value;
831
832 protected Prop(String key, String value) {
833 this.key = key;
834 this.value = value;
835 }
836 }
837
838 // Auxiliary properties separator
Thomas Vachuska583bc632015-04-14 10:10:57 -0700839 static class Separator extends Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700840 protected Separator() {
841 super("-", "");
842 }
843 }
844
845 // Auxiliary carrier of data for requesting traffic message.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700846 static class TrafficClass {
Thomas Vachuska329af532015-03-10 02:08:33 -0700847 public final boolean showTraffic;
848 public final String type;
849 public final Iterable<Intent> intents;
850
851 TrafficClass(String type, Iterable<Intent> intents) {
852 this(type, intents, false);
853 }
854
855 TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
856 this.type = type;
857 this.intents = intents;
858 this.showTraffic = showTraffic;
859 }
860 }
861
862}