blob: f87ad7e43ce591df2570ff545cd2cd047b23eaff [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 Hunta0ddb022015-05-01 09:53:01 -070068import org.onosproject.ui.UiMessageHandler;
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 Huntd7f7bcc2015-05-08 14:13:17 -0700104@Deprecated
Simon Hunta0ddb022015-05-01 09:53:01 -0700105public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
Thomas Vachuska329af532015-03-10 02:08:33 -0700106
Simon Huntd2747a02015-04-30 22:41:16 -0700107 protected static final Logger log =
108 LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700109
Simon Huntd2747a02015-04-30 22:41:16 -0700110 private static final ProviderId PID =
111 new ProviderId("core", "org.onosproject.core", true);
Thomas Vachuska329af532015-03-10 02:08:33 -0700112 private static final String COMPACT = "%s/%s-%s/%s";
113
114 private static final double KB = 1024;
115 private static final double MB = 1024 * KB;
116 private static final double GB = 1024 * MB;
117
118 private static final String GB_UNIT = "GB";
119 private static final String MB_UNIT = "MB";
120 private static final String KB_UNIT = "KB";
121 private static final String B_UNIT = "B";
122
123 protected ServiceDirectory directory;
124 protected ClusterService clusterService;
125 protected DeviceService deviceService;
126 protected LinkService linkService;
127 protected HostService hostService;
128 protected MastershipService mastershipService;
129 protected IntentService intentService;
130 protected FlowRuleService flowService;
131 protected StatisticService statService;
132 protected TopologyService topologyService;
133
Thomas Vachuska329af532015-03-10 02:08:33 -0700134 private String version;
135
136 // TODO: extract into an external & durable state; good enough for now and demo
137 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
138
139 /**
Thomas Vachuska329af532015-03-10 02:08:33 -0700140 * Returns read-only view of the meta-ui information.
141 *
142 * @return map of id to meta-ui mementos
143 */
144 static Map<String, ObjectNode> getMetaUi() {
145 return Collections.unmodifiableMap(metaUi);
146 }
147
148 @Override
149 public void init(UiConnection connection, ServiceDirectory directory) {
150 super.init(connection, directory);
151 this.directory = checkNotNull(directory, "Directory cannot be null");
152 clusterService = directory.get(ClusterService.class);
153 deviceService = directory.get(DeviceService.class);
154 linkService = directory.get(LinkService.class);
155 hostService = directory.get(HostService.class);
156 mastershipService = directory.get(MastershipService.class);
157 intentService = directory.get(IntentService.class);
158 flowService = directory.get(FlowRuleService.class);
159 statService = directory.get(StatisticService.class);
160 topologyService = directory.get(TopologyService.class);
161
162 String ver = directory.get(CoreService.class).version().toString();
163 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
164 }
165
Thomas Vachuska329af532015-03-10 02:08:33 -0700166 // Returns the specified set of IP addresses as a string.
167 private String ip(Set<IpAddress> ipAddresses) {
168 Iterator<IpAddress> it = ipAddresses.iterator();
169 return it.hasNext() ? it.next().toString() : "unknown";
170 }
171
172 // Produces JSON structure from annotations.
173 private JsonNode props(Annotations annotations) {
Simon Huntda580882015-05-12 20:58:18 -0700174 ObjectNode props = objectNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700175 if (annotations != null) {
176 for (String key : annotations.keys()) {
177 props.put(key, annotations.value(key));
178 }
179 }
180 return props;
181 }
182
183 // Produces an informational log message event bound to the client.
184 protected ObjectNode info(long id, String message) {
185 return message("info", id, message);
186 }
187
188 // Produces a warning log message event bound to the client.
189 protected ObjectNode warning(long id, String message) {
190 return message("warning", id, message);
191 }
192
193 // Produces an error log message event bound to the client.
194 protected ObjectNode error(long id, String message) {
195 return message("error", id, message);
196 }
197
198 // Produces a log message event bound to the client.
199 private ObjectNode message(String severity, long id, String message) {
Simon Huntda580882015-05-12 20:58:18 -0700200 ObjectNode payload = objectNode()
Simon Huntd2747a02015-04-30 22:41:16 -0700201 .put("severity", severity)
202 .put("message", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700203
Simon Huntd2747a02015-04-30 22:41:16 -0700204 return JsonUtils.envelope("message", id, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700205 }
206
207 // Produces a set of all hosts listed in the specified JSON array.
208 protected Set<Host> getHosts(ArrayNode array) {
209 Set<Host> hosts = new HashSet<>();
210 if (array != null) {
211 for (JsonNode node : array) {
212 try {
213 addHost(hosts, hostId(node.asText()));
214 } catch (IllegalArgumentException e) {
215 log.debug("Skipping ID {}", node.asText());
216 }
217 }
218 }
219 return hosts;
220 }
221
222 // Adds the specified host to the set of hosts.
223 private void addHost(Set<Host> hosts, HostId hostId) {
224 Host host = hostService.getHost(hostId);
225 if (host != null) {
226 hosts.add(host);
227 }
228 }
229
230
231 // Produces a set of all devices listed in the specified JSON array.
232 protected Set<Device> getDevices(ArrayNode array) {
233 Set<Device> devices = new HashSet<>();
234 if (array != null) {
235 for (JsonNode node : array) {
236 try {
237 addDevice(devices, deviceId(node.asText()));
238 } catch (IllegalArgumentException e) {
239 log.debug("Skipping ID {}", node.asText());
240 }
241 }
242 }
243 return devices;
244 }
245
246 private void addDevice(Set<Device> devices, DeviceId deviceId) {
247 Device device = deviceService.getDevice(deviceId);
248 if (device != null) {
249 devices.add(device);
250 }
251 }
252
253 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
254 try {
255 addHost(hosts, hostId(hover));
256 } catch (IllegalArgumentException e) {
257 try {
258 addDevice(devices, deviceId(hover));
259 } catch (IllegalArgumentException ne) {
260 log.debug("Skipping ID {}", hover);
261 }
262 }
263 }
264
265 // Produces a cluster instance message to the client.
266 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
267 ControllerNode node = event.subject();
268 int switchCount = mastershipService.getDevicesOf(node.id()).size();
Simon Huntda580882015-05-12 20:58:18 -0700269 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700270 .put("id", node.id().toString())
271 .put("ip", node.ip().toString())
272 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700273 .put("uiAttached", node.equals(clusterService.getLocalNode()))
Thomas Vachuska329af532015-03-10 02:08:33 -0700274 .put("switches", switchCount);
275
Simon Huntda580882015-05-12 20:58:18 -0700276 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700277 labels.add(node.id().toString());
278 labels.add(node.ip().toString());
279
280 // Add labels, props and stuff the payload into envelope.
281 payload.set("labels", labels);
282 addMetaUi(node.id().toString(), payload);
283
284 String type = messageType != null ? messageType :
285 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
286 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
287 "addInstance")));
Simon Huntd2747a02015-04-30 22:41:16 -0700288 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700289 }
290
291 // Produces a device event message to the client.
292 protected ObjectNode deviceMessage(DeviceEvent event) {
293 Device device = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700294 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700295 .put("id", device.id().toString())
296 .put("type", device.type().toString().toLowerCase())
297 .put("online", deviceService.isAvailable(device.id()))
298 .put("master", master(device.id()));
299
300 // Generate labels: id, chassis id, no-label, optional-name
301 String name = device.annotations().value(AnnotationKeys.NAME);
Simon Huntda580882015-05-12 20:58:18 -0700302 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700303 labels.add("");
304 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
305 labels.add(device.id().toString());
306
307 // Add labels, props and stuff the payload into envelope.
308 payload.set("labels", labels);
309 payload.set("props", props(device.annotations()));
310 addGeoLocation(device, payload);
311 addMetaUi(device.id().toString(), payload);
312
313 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
314 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
Simon Huntd2747a02015-04-30 22:41:16 -0700315 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700316 }
317
318 // Produces a link event message to the client.
319 protected ObjectNode linkMessage(LinkEvent event) {
320 Link link = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700321 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700322 .put("id", compactLinkString(link))
323 .put("type", link.type().toString().toLowerCase())
324 .put("online", link.state() == Link.State.ACTIVE)
325 .put("linkWidth", 1.2)
326 .put("src", link.src().deviceId().toString())
327 .put("srcPort", link.src().port().toString())
328 .put("dst", link.dst().deviceId().toString())
329 .put("dstPort", link.dst().port().toString());
330 String type = (event.type() == LINK_ADDED) ? "addLink" :
331 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
Simon Huntd2747a02015-04-30 22:41:16 -0700332 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700333 }
334
335 // Produces a host event message to the client.
336 protected ObjectNode hostMessage(HostEvent event) {
337 Host host = event.subject();
338 String hostType = host.annotations().value(AnnotationKeys.TYPE);
Simon Huntda580882015-05-12 20:58:18 -0700339 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700340 .put("id", host.id().toString())
341 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
342 .put("ingress", compactLinkString(edgeLink(host, true)))
343 .put("egress", compactLinkString(edgeLink(host, false)));
Simon Huntda580882015-05-12 20:58:18 -0700344 payload.set("cp", hostConnect(host.location()));
345 payload.set("labels", labels(ip(host.ipAddresses()),
Thomas Vachuska329af532015-03-10 02:08:33 -0700346 host.mac().toString()));
347 payload.set("props", props(host.annotations()));
348 addGeoLocation(host, payload);
349 addMetaUi(host.id().toString(), payload);
350
351 String type = (event.type() == HOST_ADDED) ? "addHost" :
352 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
Simon Huntd2747a02015-04-30 22:41:16 -0700353 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700354 }
355
356 // Encodes the specified host location into a JSON object.
Simon Huntda580882015-05-12 20:58:18 -0700357 private ObjectNode hostConnect(HostLocation location) {
358 return objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700359 .put("device", location.deviceId().toString())
360 .put("port", location.port().toLong());
361 }
362
363 // Encodes the specified list of labels a JSON array.
Simon Huntda580882015-05-12 20:58:18 -0700364 private ArrayNode labels(String... labels) {
365 ArrayNode json = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700366 for (String label : labels) {
367 json.add(label);
368 }
369 return json;
370 }
371
372 // Returns the name of the master node for the specified device id.
373 private String master(DeviceId deviceId) {
374 NodeId master = mastershipService.getMasterFor(deviceId);
375 return master != null ? master.toString() : "";
376 }
377
378 // Generates an edge link from the specified host location.
379 private EdgeLink edgeLink(Host host, boolean ingress) {
380 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
381 host.location(), ingress);
382 }
383
384 // Adds meta UI information for the specified object.
385 private void addMetaUi(String id, ObjectNode payload) {
386 ObjectNode meta = metaUi.get(id);
387 if (meta != null) {
388 payload.set("metaUi", meta);
389 }
390 }
391
392 // Adds a geo location JSON to the specified payload object.
393 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
394 Annotations annotations = annotated.annotations();
395 if (annotations == null) {
396 return;
397 }
398
399 String slat = annotations.value(AnnotationKeys.LATITUDE);
400 String slng = annotations.value(AnnotationKeys.LONGITUDE);
401 try {
402 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
403 double lat = Double.parseDouble(slat);
404 double lng = Double.parseDouble(slng);
Simon Huntda580882015-05-12 20:58:18 -0700405 ObjectNode loc = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700406 .put("type", "latlng").put("lat", lat).put("lng", lng);
407 payload.set("location", loc);
408 }
409 } catch (NumberFormatException e) {
410 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
411 }
412 }
413
414 // Updates meta UI information for the specified object.
Simon Huntd2747a02015-04-30 22:41:16 -0700415 protected void updateMetaUi(ObjectNode payload) {
416 metaUi.put(JsonUtils.string(payload, "id"),
417 JsonUtils.node(payload, "memento"));
Thomas Vachuska329af532015-03-10 02:08:33 -0700418 }
419
420 // Returns summary response.
421 protected ObjectNode summmaryMessage(long sid) {
422 Topology topology = topologyService.currentTopology();
Simon Huntd2747a02015-04-30 22:41:16 -0700423 return JsonUtils.envelope("showSummary", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700424 json("ONOS Summary", "node",
425 new Prop("Devices", format(topology.deviceCount())),
426 new Prop("Links", format(topology.linkCount())),
427 new Prop("Hosts", format(hostService.getHostCount())),
428 new Prop("Topology SCCs", format(topology.clusterCount())),
429 new Separator(),
430 new Prop("Intents", format(intentService.getIntentCount())),
431 new Prop("Flows", format(flowService.getFlowRuleCount())),
432 new Prop("Version", version)));
Thomas Vachuska329af532015-03-10 02:08:33 -0700433 }
434
435 // Returns device details response.
436 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
437 Device device = deviceService.getDevice(deviceId);
438 Annotations annot = device.annotations();
439 String name = annot.value(AnnotationKeys.NAME);
440 int portCount = deviceService.getPorts(deviceId).size();
441 int flowCount = getFlowCount(deviceId);
Simon Huntd2747a02015-04-30 22:41:16 -0700442 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700443 json(isNullOrEmpty(name) ? deviceId.toString() : name,
444 device.type().toString().toLowerCase(),
445 new Prop("URI", deviceId.toString()),
446 new Prop("Vendor", device.manufacturer()),
447 new Prop("H/W Version", device.hwVersion()),
448 new Prop("S/W Version", device.swVersion()),
449 new Prop("Serial Number", device.serialNumber()),
450 new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
451 new Separator(),
452 new Prop("Master", master(deviceId)),
453 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
454 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
455 new Separator(),
456 new Prop("Ports", Integer.toString(portCount)),
457 new Prop("Flows", Integer.toString(flowCount))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700458 }
459
460 protected int getFlowCount(DeviceId deviceId) {
461 int count = 0;
462 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
463 while (it.hasNext()) {
464 count++;
465 it.next();
466 }
467 return count;
468 }
469
470 // Counts all entries that egress on the given device links.
471 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
472 List<FlowEntry> entries = new ArrayList<>();
473 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
474 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
475 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
476 while (it.hasNext()) {
477 entries.add(it.next());
478 }
479
480 // Add all edge links to the set
481 if (hosts != null) {
482 for (Host host : hosts) {
483 links.add(new DefaultEdgeLink(host.providerId(),
484 new ConnectPoint(host.id(), P0),
485 host.location(), false));
486 }
487 }
488
489 Map<Link, Integer> counts = new HashMap<>();
490 for (Link link : links) {
491 counts.put(link, getEgressFlows(link, entries));
492 }
493 return counts;
494 }
495
496 // Counts all entries that egress on the link source port.
497 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
498 int count = 0;
499 PortNumber out = link.src().port();
500 for (FlowEntry entry : entries) {
501 TrafficTreatment treatment = entry.treatment();
Ray Milkey42507352015-03-20 15:16:10 -0700502 for (Instruction instruction : treatment.allInstructions()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700503 if (instruction.type() == Instruction.Type.OUTPUT &&
504 ((OutputInstruction) instruction).port().equals(out)) {
505 count++;
506 }
507 }
508 }
509 return count;
510 }
511
512
513 // Returns host details response.
514 protected ObjectNode hostDetails(HostId hostId, long sid) {
515 Host host = hostService.getHost(hostId);
516 Annotations annot = host.annotations();
517 String type = annot.value(AnnotationKeys.TYPE);
518 String name = annot.value(AnnotationKeys.NAME);
519 String vlan = host.vlan().toString();
Simon Huntd2747a02015-04-30 22:41:16 -0700520 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700521 json(isNullOrEmpty(name) ? hostId.toString() : name,
522 isNullOrEmpty(type) ? "endstation" : type,
523 new Prop("MAC", host.mac().toString()),
524 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
525 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
526 new Separator(),
527 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
528 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700529 }
530
531
532 // Produces JSON message to trigger traffic overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700533 protected ObjectNode trafficSummaryMessage() {
Simon Huntda580882015-05-12 20:58:18 -0700534 ObjectNode payload = objectNode();
535 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700536 payload.set("paths", paths);
537
Simon Huntda580882015-05-12 20:58:18 -0700538 ObjectNode pathNodeN = objectNode();
539 ArrayNode linksNodeN = arrayNode();
540 ArrayNode labelsN = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700541
542 pathNodeN.put("class", "plain").put("traffic", false);
543 pathNodeN.set("links", linksNodeN);
544 pathNodeN.set("labels", labelsN);
545 paths.add(pathNodeN);
546
Simon Huntda580882015-05-12 20:58:18 -0700547 ObjectNode pathNodeT = objectNode();
548 ArrayNode linksNodeT = arrayNode();
549 ArrayNode labelsT = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700550
551 pathNodeT.put("class", "secondary").put("traffic", true);
552 pathNodeT.set("links", linksNodeT);
553 pathNodeT.set("labels", labelsT);
554 paths.add(pathNodeT);
555
556 for (BiLink link : consolidateLinks(linkService.getLinks())) {
557 boolean bi = link.two != null;
558 if (isInfrastructureEgress(link.one) ||
559 (bi && isInfrastructureEgress(link.two))) {
560 link.addLoad(statService.load(link.one));
561 link.addLoad(bi ? statService.load(link.two) : null);
562 if (link.hasTraffic) {
563 linksNodeT.add(compactLinkString(link.one));
564 labelsT.add(formatBytes(link.bytes));
565 } else {
566 linksNodeN.add(compactLinkString(link.one));
567 labelsN.add("");
568 }
569 }
570 }
Simon Huntd2747a02015-04-30 22:41:16 -0700571 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700572 }
573
574 private Collection<BiLink> consolidateLinks(Iterable<Link> links) {
575 Map<LinkKey, BiLink> biLinks = new HashMap<>();
576 for (Link link : links) {
577 addLink(biLinks, link);
578 }
579 return biLinks.values();
580 }
581
582 // Produces JSON message to trigger flow overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700583 protected ObjectNode flowSummaryMessage(Set<Device> devices) {
Simon Huntda580882015-05-12 20:58:18 -0700584 ObjectNode payload = objectNode();
585 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700586 payload.set("paths", paths);
587
588 for (Device device : devices) {
589 Map<Link, Integer> counts = getFlowCounts(device.id());
590 for (Link link : counts.keySet()) {
591 addLinkFlows(link, paths, counts.get(link));
592 }
593 }
Simon Huntd2747a02015-04-30 22:41:16 -0700594 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700595 }
596
597 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
Simon Huntda580882015-05-12 20:58:18 -0700598 ObjectNode pathNode = objectNode();
599 ArrayNode linksNode = arrayNode();
600 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700601 boolean noFlows = count == null || count == 0;
602 pathNode.put("class", noFlows ? "secondary" : "primary");
603 pathNode.put("traffic", false);
604 pathNode.set("links", linksNode.add(compactLinkString(link)));
605 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
606 (count == 1 ? " flow" : " flows"))));
607 paths.add(pathNode);
608 }
609
610
611 // Produces JSON message to trigger traffic visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700612 protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
Simon Huntda580882015-05-12 20:58:18 -0700613 ObjectNode payload = objectNode();
614 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700615 payload.set("paths", paths);
616
617 // Classify links based on their traffic traffic first...
618 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
619
620 // Then separate the links into their respective classes and send them out.
621 Map<String, ObjectNode> pathNodes = new HashMap<>();
622 for (BiLink biLink : biLinks.values()) {
623 boolean hasTraffic = biLink.hasTraffic;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700624 String tc = (biLink.classes() + (hasTraffic ? " animated" : "")).trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700625 ObjectNode pathNode = pathNodes.get(tc);
626 if (pathNode == null) {
Simon Huntda580882015-05-12 20:58:18 -0700627 pathNode = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700628 .put("class", tc).put("traffic", hasTraffic);
Simon Huntda580882015-05-12 20:58:18 -0700629 pathNode.set("links", arrayNode());
630 pathNode.set("labels", arrayNode());
Thomas Vachuska329af532015-03-10 02:08:33 -0700631 pathNodes.put(tc, pathNode);
632 paths.add(pathNode);
633 }
634 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
635 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
636 }
637
Simon Huntd2747a02015-04-30 22:41:16 -0700638 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700639 }
640
641 // Classifies the link traffic according to the specified classes.
642 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
643 Map<LinkKey, BiLink> biLinks = new HashMap<>();
644 for (TrafficClass trafficClass : trafficClasses) {
645 for (Intent intent : trafficClass.intents) {
646 boolean isOptical = intent instanceof OpticalConnectivityIntent;
647 List<Intent> installables = intentService.getInstallableIntents(intent.key());
648 if (installables != null) {
649 for (Intent installable : installables) {
650 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
651 if (installable instanceof PathIntent) {
652 classifyLinks(type, biLinks, trafficClass.showTraffic,
653 ((PathIntent) installable).path().links());
654 } else if (installable instanceof LinkCollectionIntent) {
655 classifyLinks(type, biLinks, trafficClass.showTraffic,
656 ((LinkCollectionIntent) installable).links());
657 } else if (installable instanceof OpticalPathIntent) {
658 classifyLinks(type, biLinks, trafficClass.showTraffic,
659 ((OpticalPathIntent) installable).path().links());
660 }
661 }
662 }
663 }
664 }
665 return biLinks;
666 }
667
668
669 // Adds the link segments (path or tree) associated with the specified
670 // connectivity intent
671 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
672 boolean showTraffic, Iterable<Link> links) {
673 if (links != null) {
674 for (Link link : links) {
675 BiLink biLink = addLink(biLinks, link);
676 if (isInfrastructureEgress(link)) {
677 if (showTraffic) {
678 biLink.addLoad(statService.load(link));
679 }
680 biLink.addClass(type);
681 }
682 }
683 }
684 }
685
686
Thomas Vachuska583bc632015-04-14 10:10:57 -0700687 static BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700688 LinkKey key = canonicalLinkKey(link);
689 BiLink biLink = biLinks.get(key);
690 if (biLink != null) {
691 biLink.setOther(link);
692 } else {
693 biLink = new BiLink(key, link);
694 biLinks.put(key, biLink);
695 }
696 return biLink;
697 }
698
699
700 // Adds the link segments (path or tree) associated with the specified
701 // connectivity intent
702 protected void addPathTraffic(ArrayNode paths, String type, String trafficType,
703 Iterable<Link> links) {
Simon Huntda580882015-05-12 20:58:18 -0700704 ObjectNode pathNode = objectNode();
705 ArrayNode linksNode = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700706
707 if (links != null) {
Simon Huntda580882015-05-12 20:58:18 -0700708 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700709 boolean hasTraffic = false;
710 for (Link link : links) {
711 if (isInfrastructureEgress(link)) {
712 linksNode.add(compactLinkString(link));
713 Load load = statService.load(link);
714 String label = "";
715 if (load.rate() > 0) {
716 hasTraffic = true;
717 label = formatBytes(load.latest());
718 }
719 labels.add(label);
720 }
721 }
722 pathNode.put("class", hasTraffic ? type + " " + trafficType : type);
723 pathNode.put("traffic", hasTraffic);
724 pathNode.set("links", linksNode);
725 pathNode.set("labels", labels);
726 paths.add(pathNode);
727 }
728 }
729
730 // Poor-mans formatting to get the labels with byte counts looking nice.
731 private String formatBytes(long bytes) {
732 String unit;
733 double value;
734 if (bytes > GB) {
735 value = bytes / GB;
736 unit = GB_UNIT;
737 } else if (bytes > MB) {
738 value = bytes / MB;
739 unit = MB_UNIT;
740 } else if (bytes > KB) {
741 value = bytes / KB;
742 unit = KB_UNIT;
743 } else {
744 value = bytes;
745 unit = B_UNIT;
746 }
747 DecimalFormat format = new DecimalFormat("#,###.##");
748 return format.format(value) + " " + unit;
749 }
750
751 // Formats the given number into a string.
752 private String format(Number number) {
753 DecimalFormat format = new DecimalFormat("#,###");
754 return format.format(number);
755 }
756
757 private boolean isInfrastructureEgress(Link link) {
758 return link.src().elementId() instanceof DeviceId;
759 }
760
761 // Produces compact string representation of a link.
762 private static String compactLinkString(Link link) {
763 return String.format(COMPACT, link.src().elementId(), link.src().port(),
764 link.dst().elementId(), link.dst().port());
765 }
766
767 // Produces JSON property details.
768 private ObjectNode json(String id, String type, Prop... props) {
769 ObjectMapper mapper = new ObjectMapper();
Simon Huntda580882015-05-12 20:58:18 -0700770 ObjectNode result = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700771 .put("id", id).put("type", type);
Simon Huntda580882015-05-12 20:58:18 -0700772 ObjectNode pnode = objectNode();
773 ArrayNode porder = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700774 for (Prop p : props) {
775 porder.add(p.key);
776 pnode.put(p.key, p.value);
777 }
778 result.set("propOrder", porder);
779 result.set("props", pnode);
780 return result;
781 }
782
783 // Produces canonical link key, i.e. one that will match link and its inverse.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700784 static LinkKey canonicalLinkKey(Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700785 String sn = link.src().elementId().toString();
786 String dn = link.dst().elementId().toString();
787 return sn.compareTo(dn) < 0 ?
788 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
789 }
790
791 // Representation of link and its inverse and any traffic data.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700792 static class BiLink {
Thomas Vachuska329af532015-03-10 02:08:33 -0700793 public final LinkKey key;
794 public final Link one;
795 public Link two;
796 public boolean hasTraffic = false;
797 public long bytes = 0;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700798
799 private Set<String> classes = new HashSet<>();
Thomas Vachuska329af532015-03-10 02:08:33 -0700800
801 BiLink(LinkKey key, Link link) {
802 this.key = key;
803 this.one = link;
804 }
805
806 void setOther(Link link) {
807 this.two = link;
808 }
809
810 void addLoad(Load load) {
811 if (load != null) {
812 this.hasTraffic = hasTraffic || load.rate() > 0;
813 this.bytes += load.latest();
814 }
815 }
816
817 void addClass(String trafficClass) {
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700818 classes.add(trafficClass);
819 }
820
821 String classes() {
822 StringBuilder sb = new StringBuilder();
823 classes.forEach(c -> sb.append(c).append(" "));
824 return sb.toString().trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700825 }
826 }
827
828 // Auxiliary key/value carrier.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700829 static class Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700830 public final String key;
831 public final String value;
832
833 protected Prop(String key, String value) {
834 this.key = key;
835 this.value = value;
836 }
837 }
838
839 // Auxiliary properties separator
Thomas Vachuska583bc632015-04-14 10:10:57 -0700840 static class Separator extends Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700841 protected Separator() {
842 super("-", "");
843 }
844 }
845
846 // Auxiliary carrier of data for requesting traffic message.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700847 static class TrafficClass {
Thomas Vachuska329af532015-03-10 02:08:33 -0700848 public final boolean showTraffic;
849 public final String type;
850 public final Iterable<Intent> intents;
851
852 TrafficClass(String type, Iterable<Intent> intents) {
853 this(type, intents, false);
854 }
855
856 TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
857 this.type = type;
858 this.intents = intents;
859 this.showTraffic = showTraffic;
860 }
861 }
862
863}