blob: c7ebf359c291a380bd597add75893f1136f21375 [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;
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -070022import com.google.common.collect.ImmutableList;
Thomas Vachuska329af532015-03-10 02:08:33 -070023import org.onlab.osgi.ServiceDirectory;
24import org.onlab.packet.IpAddress;
25import org.onosproject.cluster.ClusterEvent;
26import org.onosproject.cluster.ClusterService;
27import org.onosproject.cluster.ControllerNode;
28import org.onosproject.cluster.NodeId;
29import org.onosproject.core.CoreService;
30import org.onosproject.mastership.MastershipService;
31import org.onosproject.net.Annotated;
32import org.onosproject.net.AnnotationKeys;
33import org.onosproject.net.Annotations;
34import org.onosproject.net.ConnectPoint;
35import org.onosproject.net.DefaultEdgeLink;
36import org.onosproject.net.Device;
37import org.onosproject.net.DeviceId;
38import org.onosproject.net.EdgeLink;
39import org.onosproject.net.Host;
40import org.onosproject.net.HostId;
41import org.onosproject.net.HostLocation;
42import org.onosproject.net.Link;
43import org.onosproject.net.LinkKey;
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -070044import org.onosproject.net.NetworkResource;
Thomas Vachuska329af532015-03-10 02:08:33 -070045import org.onosproject.net.PortNumber;
46import org.onosproject.net.device.DeviceEvent;
47import org.onosproject.net.device.DeviceService;
48import org.onosproject.net.flow.FlowEntry;
49import org.onosproject.net.flow.FlowRuleService;
50import org.onosproject.net.flow.TrafficTreatment;
51import org.onosproject.net.flow.instructions.Instruction;
52import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
53import org.onosproject.net.host.HostEvent;
54import org.onosproject.net.host.HostService;
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -070055import org.onosproject.net.intent.FlowRuleIntent;
Thomas Vachuska329af532015-03-10 02:08:33 -070056import org.onosproject.net.intent.Intent;
57import org.onosproject.net.intent.IntentService;
58import org.onosproject.net.intent.LinkCollectionIntent;
59import org.onosproject.net.intent.OpticalConnectivityIntent;
60import org.onosproject.net.intent.OpticalPathIntent;
61import org.onosproject.net.intent.PathIntent;
62import org.onosproject.net.link.LinkEvent;
63import org.onosproject.net.link.LinkService;
64import org.onosproject.net.provider.ProviderId;
65import org.onosproject.net.statistic.Load;
66import org.onosproject.net.statistic.StatisticService;
67import org.onosproject.net.topology.Topology;
68import org.onosproject.net.topology.TopologyService;
Simon Huntd2747a02015-04-30 22:41:16 -070069import org.onosproject.ui.JsonUtils;
Thomas Vachuska329af532015-03-10 02:08:33 -070070import org.onosproject.ui.UiConnection;
Simon Hunta0ddb022015-05-01 09:53:01 -070071import org.onosproject.ui.UiMessageHandler;
Thomas Vachuska329af532015-03-10 02:08:33 -070072import org.slf4j.Logger;
73import org.slf4j.LoggerFactory;
74
75import java.text.DecimalFormat;
76import java.util.ArrayList;
77import java.util.Collection;
78import java.util.Collections;
79import java.util.HashMap;
80import java.util.HashSet;
81import java.util.Iterator;
82import java.util.List;
83import java.util.Map;
84import java.util.Set;
85import java.util.concurrent.ConcurrentHashMap;
86
87import static com.google.common.base.Preconditions.checkNotNull;
88import static com.google.common.base.Strings.isNullOrEmpty;
89import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
90import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
91import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
92import static org.onosproject.net.DeviceId.deviceId;
93import static org.onosproject.net.HostId.hostId;
94import static org.onosproject.net.LinkKey.linkKey;
95import static org.onosproject.net.PortNumber.P0;
96import static org.onosproject.net.PortNumber.portNumber;
97import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
98import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
99import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
100import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
101import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
102import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
103
104/**
105 * Facility for creating messages bound for the topology viewer.
106 */
Simon Huntd7f7bcc2015-05-08 14:13:17 -0700107@Deprecated
Simon Hunta0ddb022015-05-01 09:53:01 -0700108public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
Thomas Vachuska329af532015-03-10 02:08:33 -0700109
Simon Huntd2747a02015-04-30 22:41:16 -0700110 protected static final Logger log =
111 LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700112
Simon Huntd2747a02015-04-30 22:41:16 -0700113 private static final ProviderId PID =
114 new ProviderId("core", "org.onosproject.core", true);
Thomas Vachuska329af532015-03-10 02:08:33 -0700115 private static final String COMPACT = "%s/%s-%s/%s";
116
117 private static final double KB = 1024;
118 private static final double MB = 1024 * KB;
119 private static final double GB = 1024 * MB;
120
121 private static final String GB_UNIT = "GB";
122 private static final String MB_UNIT = "MB";
123 private static final String KB_UNIT = "KB";
124 private static final String B_UNIT = "B";
125
126 protected ServiceDirectory directory;
127 protected ClusterService clusterService;
128 protected DeviceService deviceService;
129 protected LinkService linkService;
130 protected HostService hostService;
131 protected MastershipService mastershipService;
132 protected IntentService intentService;
133 protected FlowRuleService flowService;
134 protected StatisticService statService;
135 protected TopologyService topologyService;
136
Thomas Vachuska329af532015-03-10 02:08:33 -0700137 private String version;
138
139 // TODO: extract into an external & durable state; good enough for now and demo
140 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
141
142 /**
Thomas Vachuska329af532015-03-10 02:08:33 -0700143 * Returns read-only view of the meta-ui information.
144 *
145 * @return map of id to meta-ui mementos
146 */
147 static Map<String, ObjectNode> getMetaUi() {
148 return Collections.unmodifiableMap(metaUi);
149 }
150
151 @Override
152 public void init(UiConnection connection, ServiceDirectory directory) {
153 super.init(connection, directory);
154 this.directory = checkNotNull(directory, "Directory cannot be null");
155 clusterService = directory.get(ClusterService.class);
156 deviceService = directory.get(DeviceService.class);
157 linkService = directory.get(LinkService.class);
158 hostService = directory.get(HostService.class);
159 mastershipService = directory.get(MastershipService.class);
160 intentService = directory.get(IntentService.class);
161 flowService = directory.get(FlowRuleService.class);
162 statService = directory.get(StatisticService.class);
163 topologyService = directory.get(TopologyService.class);
164
165 String ver = directory.get(CoreService.class).version().toString();
166 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
167 }
168
Thomas Vachuska329af532015-03-10 02:08:33 -0700169 // Returns the specified set of IP addresses as a string.
170 private String ip(Set<IpAddress> ipAddresses) {
171 Iterator<IpAddress> it = ipAddresses.iterator();
172 return it.hasNext() ? it.next().toString() : "unknown";
173 }
174
175 // Produces JSON structure from annotations.
176 private JsonNode props(Annotations annotations) {
Simon Huntda580882015-05-12 20:58:18 -0700177 ObjectNode props = objectNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700178 if (annotations != null) {
179 for (String key : annotations.keys()) {
180 props.put(key, annotations.value(key));
181 }
182 }
183 return props;
184 }
185
186 // Produces an informational log message event bound to the client.
187 protected ObjectNode info(long id, String message) {
188 return message("info", id, message);
189 }
190
191 // Produces a warning log message event bound to the client.
192 protected ObjectNode warning(long id, String message) {
193 return message("warning", id, message);
194 }
195
196 // Produces an error log message event bound to the client.
197 protected ObjectNode error(long id, String message) {
198 return message("error", id, message);
199 }
200
201 // Produces a log message event bound to the client.
202 private ObjectNode message(String severity, long id, String message) {
Simon Huntda580882015-05-12 20:58:18 -0700203 ObjectNode payload = objectNode()
Simon Huntd2747a02015-04-30 22:41:16 -0700204 .put("severity", severity)
205 .put("message", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700206
Simon Huntd2747a02015-04-30 22:41:16 -0700207 return JsonUtils.envelope("message", id, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700208 }
209
210 // Produces a set of all hosts listed in the specified JSON array.
211 protected Set<Host> getHosts(ArrayNode array) {
212 Set<Host> hosts = new HashSet<>();
213 if (array != null) {
214 for (JsonNode node : array) {
215 try {
216 addHost(hosts, hostId(node.asText()));
217 } catch (IllegalArgumentException e) {
218 log.debug("Skipping ID {}", node.asText());
219 }
220 }
221 }
222 return hosts;
223 }
224
225 // Adds the specified host to the set of hosts.
226 private void addHost(Set<Host> hosts, HostId hostId) {
227 Host host = hostService.getHost(hostId);
228 if (host != null) {
229 hosts.add(host);
230 }
231 }
232
233
234 // Produces a set of all devices listed in the specified JSON array.
235 protected Set<Device> getDevices(ArrayNode array) {
236 Set<Device> devices = new HashSet<>();
237 if (array != null) {
238 for (JsonNode node : array) {
239 try {
240 addDevice(devices, deviceId(node.asText()));
241 } catch (IllegalArgumentException e) {
242 log.debug("Skipping ID {}", node.asText());
243 }
244 }
245 }
246 return devices;
247 }
248
249 private void addDevice(Set<Device> devices, DeviceId deviceId) {
250 Device device = deviceService.getDevice(deviceId);
251 if (device != null) {
252 devices.add(device);
253 }
254 }
255
256 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
257 try {
258 addHost(hosts, hostId(hover));
259 } catch (IllegalArgumentException e) {
260 try {
261 addDevice(devices, deviceId(hover));
262 } catch (IllegalArgumentException ne) {
263 log.debug("Skipping ID {}", hover);
264 }
265 }
266 }
267
268 // Produces a cluster instance message to the client.
269 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
270 ControllerNode node = event.subject();
271 int switchCount = mastershipService.getDevicesOf(node.id()).size();
Simon Huntda580882015-05-12 20:58:18 -0700272 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700273 .put("id", node.id().toString())
274 .put("ip", node.ip().toString())
275 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700276 .put("uiAttached", node.equals(clusterService.getLocalNode()))
Thomas Vachuska329af532015-03-10 02:08:33 -0700277 .put("switches", switchCount);
278
Simon Huntda580882015-05-12 20:58:18 -0700279 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700280 labels.add(node.id().toString());
281 labels.add(node.ip().toString());
282
283 // Add labels, props and stuff the payload into envelope.
284 payload.set("labels", labels);
285 addMetaUi(node.id().toString(), payload);
286
287 String type = messageType != null ? messageType :
288 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
289 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
290 "addInstance")));
Simon Huntd2747a02015-04-30 22:41:16 -0700291 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700292 }
293
294 // Produces a device event message to the client.
295 protected ObjectNode deviceMessage(DeviceEvent event) {
296 Device device = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700297 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700298 .put("id", device.id().toString())
299 .put("type", device.type().toString().toLowerCase())
300 .put("online", deviceService.isAvailable(device.id()))
301 .put("master", master(device.id()));
302
303 // Generate labels: id, chassis id, no-label, optional-name
304 String name = device.annotations().value(AnnotationKeys.NAME);
Simon Huntda580882015-05-12 20:58:18 -0700305 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700306 labels.add("");
307 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
308 labels.add(device.id().toString());
309
310 // Add labels, props and stuff the payload into envelope.
311 payload.set("labels", labels);
312 payload.set("props", props(device.annotations()));
313 addGeoLocation(device, payload);
314 addMetaUi(device.id().toString(), payload);
315
316 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
317 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
Simon Huntd2747a02015-04-30 22:41:16 -0700318 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700319 }
320
321 // Produces a link event message to the client.
322 protected ObjectNode linkMessage(LinkEvent event) {
323 Link link = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700324 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700325 .put("id", compactLinkString(link))
326 .put("type", link.type().toString().toLowerCase())
327 .put("online", link.state() == Link.State.ACTIVE)
328 .put("linkWidth", 1.2)
329 .put("src", link.src().deviceId().toString())
330 .put("srcPort", link.src().port().toString())
331 .put("dst", link.dst().deviceId().toString())
332 .put("dstPort", link.dst().port().toString());
333 String type = (event.type() == LINK_ADDED) ? "addLink" :
334 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
Simon Huntd2747a02015-04-30 22:41:16 -0700335 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700336 }
337
338 // Produces a host event message to the client.
339 protected ObjectNode hostMessage(HostEvent event) {
340 Host host = event.subject();
341 String hostType = host.annotations().value(AnnotationKeys.TYPE);
Simon Huntda580882015-05-12 20:58:18 -0700342 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700343 .put("id", host.id().toString())
344 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
345 .put("ingress", compactLinkString(edgeLink(host, true)))
346 .put("egress", compactLinkString(edgeLink(host, false)));
Simon Huntda580882015-05-12 20:58:18 -0700347 payload.set("cp", hostConnect(host.location()));
348 payload.set("labels", labels(ip(host.ipAddresses()),
Thomas Vachuska329af532015-03-10 02:08:33 -0700349 host.mac().toString()));
350 payload.set("props", props(host.annotations()));
351 addGeoLocation(host, payload);
352 addMetaUi(host.id().toString(), payload);
353
354 String type = (event.type() == HOST_ADDED) ? "addHost" :
355 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
Simon Huntd2747a02015-04-30 22:41:16 -0700356 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700357 }
358
359 // Encodes the specified host location into a JSON object.
Simon Huntda580882015-05-12 20:58:18 -0700360 private ObjectNode hostConnect(HostLocation location) {
361 return objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700362 .put("device", location.deviceId().toString())
363 .put("port", location.port().toLong());
364 }
365
366 // Encodes the specified list of labels a JSON array.
Simon Huntda580882015-05-12 20:58:18 -0700367 private ArrayNode labels(String... labels) {
368 ArrayNode json = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700369 for (String label : labels) {
370 json.add(label);
371 }
372 return json;
373 }
374
375 // Returns the name of the master node for the specified device id.
376 private String master(DeviceId deviceId) {
377 NodeId master = mastershipService.getMasterFor(deviceId);
378 return master != null ? master.toString() : "";
379 }
380
381 // Generates an edge link from the specified host location.
382 private EdgeLink edgeLink(Host host, boolean ingress) {
383 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
384 host.location(), ingress);
385 }
386
387 // Adds meta UI information for the specified object.
388 private void addMetaUi(String id, ObjectNode payload) {
389 ObjectNode meta = metaUi.get(id);
390 if (meta != null) {
391 payload.set("metaUi", meta);
392 }
393 }
394
395 // Adds a geo location JSON to the specified payload object.
396 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
397 Annotations annotations = annotated.annotations();
398 if (annotations == null) {
399 return;
400 }
401
402 String slat = annotations.value(AnnotationKeys.LATITUDE);
403 String slng = annotations.value(AnnotationKeys.LONGITUDE);
404 try {
405 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
406 double lat = Double.parseDouble(slat);
407 double lng = Double.parseDouble(slng);
Simon Huntda580882015-05-12 20:58:18 -0700408 ObjectNode loc = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700409 .put("type", "latlng").put("lat", lat).put("lng", lng);
410 payload.set("location", loc);
411 }
412 } catch (NumberFormatException e) {
413 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
414 }
415 }
416
417 // Updates meta UI information for the specified object.
Simon Huntd2747a02015-04-30 22:41:16 -0700418 protected void updateMetaUi(ObjectNode payload) {
419 metaUi.put(JsonUtils.string(payload, "id"),
420 JsonUtils.node(payload, "memento"));
Thomas Vachuska329af532015-03-10 02:08:33 -0700421 }
422
423 // Returns summary response.
424 protected ObjectNode summmaryMessage(long sid) {
425 Topology topology = topologyService.currentTopology();
Simon Huntd2747a02015-04-30 22:41:16 -0700426 return JsonUtils.envelope("showSummary", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700427 json("ONOS Summary", "node",
428 new Prop("Devices", format(topology.deviceCount())),
429 new Prop("Links", format(topology.linkCount())),
430 new Prop("Hosts", format(hostService.getHostCount())),
431 new Prop("Topology SCCs", format(topology.clusterCount())),
432 new Separator(),
433 new Prop("Intents", format(intentService.getIntentCount())),
434 new Prop("Flows", format(flowService.getFlowRuleCount())),
435 new Prop("Version", version)));
Thomas Vachuska329af532015-03-10 02:08:33 -0700436 }
437
438 // Returns device details response.
439 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
440 Device device = deviceService.getDevice(deviceId);
441 Annotations annot = device.annotations();
442 String name = annot.value(AnnotationKeys.NAME);
443 int portCount = deviceService.getPorts(deviceId).size();
444 int flowCount = getFlowCount(deviceId);
Simon Huntd2747a02015-04-30 22:41:16 -0700445 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700446 json(isNullOrEmpty(name) ? deviceId.toString() : name,
447 device.type().toString().toLowerCase(),
448 new Prop("URI", deviceId.toString()),
449 new Prop("Vendor", device.manufacturer()),
450 new Prop("H/W Version", device.hwVersion()),
451 new Prop("S/W Version", device.swVersion()),
452 new Prop("Serial Number", device.serialNumber()),
453 new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
454 new Separator(),
455 new Prop("Master", master(deviceId)),
456 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
457 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
458 new Separator(),
459 new Prop("Ports", Integer.toString(portCount)),
460 new Prop("Flows", Integer.toString(flowCount))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700461 }
462
463 protected int getFlowCount(DeviceId deviceId) {
464 int count = 0;
465 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
466 while (it.hasNext()) {
467 count++;
468 it.next();
469 }
470 return count;
471 }
472
473 // Counts all entries that egress on the given device links.
474 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
475 List<FlowEntry> entries = new ArrayList<>();
476 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
477 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
478 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
479 while (it.hasNext()) {
480 entries.add(it.next());
481 }
482
483 // Add all edge links to the set
484 if (hosts != null) {
485 for (Host host : hosts) {
486 links.add(new DefaultEdgeLink(host.providerId(),
487 new ConnectPoint(host.id(), P0),
488 host.location(), false));
489 }
490 }
491
492 Map<Link, Integer> counts = new HashMap<>();
493 for (Link link : links) {
494 counts.put(link, getEgressFlows(link, entries));
495 }
496 return counts;
497 }
498
499 // Counts all entries that egress on the link source port.
500 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
501 int count = 0;
502 PortNumber out = link.src().port();
503 for (FlowEntry entry : entries) {
504 TrafficTreatment treatment = entry.treatment();
Ray Milkey42507352015-03-20 15:16:10 -0700505 for (Instruction instruction : treatment.allInstructions()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700506 if (instruction.type() == Instruction.Type.OUTPUT &&
507 ((OutputInstruction) instruction).port().equals(out)) {
508 count++;
509 }
510 }
511 }
512 return count;
513 }
514
515
516 // Returns host details response.
517 protected ObjectNode hostDetails(HostId hostId, long sid) {
518 Host host = hostService.getHost(hostId);
519 Annotations annot = host.annotations();
520 String type = annot.value(AnnotationKeys.TYPE);
521 String name = annot.value(AnnotationKeys.NAME);
522 String vlan = host.vlan().toString();
Simon Huntd2747a02015-04-30 22:41:16 -0700523 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700524 json(isNullOrEmpty(name) ? hostId.toString() : name,
525 isNullOrEmpty(type) ? "endstation" : type,
526 new Prop("MAC", host.mac().toString()),
527 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
528 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
529 new Separator(),
530 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
531 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700532 }
533
534
535 // Produces JSON message to trigger traffic overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700536 protected ObjectNode trafficSummaryMessage() {
Simon Huntda580882015-05-12 20:58:18 -0700537 ObjectNode payload = objectNode();
538 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700539 payload.set("paths", paths);
540
Simon Huntda580882015-05-12 20:58:18 -0700541 ObjectNode pathNodeN = objectNode();
542 ArrayNode linksNodeN = arrayNode();
543 ArrayNode labelsN = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700544
545 pathNodeN.put("class", "plain").put("traffic", false);
546 pathNodeN.set("links", linksNodeN);
547 pathNodeN.set("labels", labelsN);
548 paths.add(pathNodeN);
549
Simon Huntda580882015-05-12 20:58:18 -0700550 ObjectNode pathNodeT = objectNode();
551 ArrayNode linksNodeT = arrayNode();
552 ArrayNode labelsT = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700553
554 pathNodeT.put("class", "secondary").put("traffic", true);
555 pathNodeT.set("links", linksNodeT);
556 pathNodeT.set("labels", labelsT);
557 paths.add(pathNodeT);
558
559 for (BiLink link : consolidateLinks(linkService.getLinks())) {
560 boolean bi = link.two != null;
561 if (isInfrastructureEgress(link.one) ||
562 (bi && isInfrastructureEgress(link.two))) {
563 link.addLoad(statService.load(link.one));
564 link.addLoad(bi ? statService.load(link.two) : null);
565 if (link.hasTraffic) {
566 linksNodeT.add(compactLinkString(link.one));
567 labelsT.add(formatBytes(link.bytes));
568 } else {
569 linksNodeN.add(compactLinkString(link.one));
570 labelsN.add("");
571 }
572 }
573 }
Simon Huntd2747a02015-04-30 22:41:16 -0700574 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700575 }
576
577 private Collection<BiLink> consolidateLinks(Iterable<Link> links) {
578 Map<LinkKey, BiLink> biLinks = new HashMap<>();
579 for (Link link : links) {
580 addLink(biLinks, link);
581 }
582 return biLinks.values();
583 }
584
585 // Produces JSON message to trigger flow overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700586 protected ObjectNode flowSummaryMessage(Set<Device> devices) {
Simon Huntda580882015-05-12 20:58:18 -0700587 ObjectNode payload = objectNode();
588 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700589 payload.set("paths", paths);
590
591 for (Device device : devices) {
592 Map<Link, Integer> counts = getFlowCounts(device.id());
593 for (Link link : counts.keySet()) {
594 addLinkFlows(link, paths, counts.get(link));
595 }
596 }
Simon Huntd2747a02015-04-30 22:41:16 -0700597 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700598 }
599
600 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
Simon Huntda580882015-05-12 20:58:18 -0700601 ObjectNode pathNode = objectNode();
602 ArrayNode linksNode = arrayNode();
603 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700604 boolean noFlows = count == null || count == 0;
605 pathNode.put("class", noFlows ? "secondary" : "primary");
606 pathNode.put("traffic", false);
607 pathNode.set("links", linksNode.add(compactLinkString(link)));
608 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
609 (count == 1 ? " flow" : " flows"))));
610 paths.add(pathNode);
611 }
612
613
614 // Produces JSON message to trigger traffic visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700615 protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
Simon Huntda580882015-05-12 20:58:18 -0700616 ObjectNode payload = objectNode();
617 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700618 payload.set("paths", paths);
619
620 // Classify links based on their traffic traffic first...
621 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
622
623 // Then separate the links into their respective classes and send them out.
624 Map<String, ObjectNode> pathNodes = new HashMap<>();
625 for (BiLink biLink : biLinks.values()) {
626 boolean hasTraffic = biLink.hasTraffic;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700627 String tc = (biLink.classes() + (hasTraffic ? " animated" : "")).trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700628 ObjectNode pathNode = pathNodes.get(tc);
629 if (pathNode == null) {
Simon Huntda580882015-05-12 20:58:18 -0700630 pathNode = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700631 .put("class", tc).put("traffic", hasTraffic);
Simon Huntda580882015-05-12 20:58:18 -0700632 pathNode.set("links", arrayNode());
633 pathNode.set("labels", arrayNode());
Thomas Vachuska329af532015-03-10 02:08:33 -0700634 pathNodes.put(tc, pathNode);
635 paths.add(pathNode);
636 }
637 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
638 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
639 }
640
Simon Huntd2747a02015-04-30 22:41:16 -0700641 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700642 }
643
644 // Classifies the link traffic according to the specified classes.
645 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
646 Map<LinkKey, BiLink> biLinks = new HashMap<>();
647 for (TrafficClass trafficClass : trafficClasses) {
648 for (Intent intent : trafficClass.intents) {
649 boolean isOptical = intent instanceof OpticalConnectivityIntent;
650 List<Intent> installables = intentService.getInstallableIntents(intent.key());
651 if (installables != null) {
652 for (Intent installable : installables) {
653 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
654 if (installable instanceof PathIntent) {
655 classifyLinks(type, biLinks, trafficClass.showTraffic,
656 ((PathIntent) installable).path().links());
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700657 } else if (installable instanceof FlowRuleIntent) {
658 classifyLinks(type, biLinks, trafficClass.showTraffic,
659 linkResources(installable));
Thomas Vachuska329af532015-03-10 02:08:33 -0700660 } else if (installable instanceof LinkCollectionIntent) {
661 classifyLinks(type, biLinks, trafficClass.showTraffic,
662 ((LinkCollectionIntent) installable).links());
663 } else if (installable instanceof OpticalPathIntent) {
664 classifyLinks(type, biLinks, trafficClass.showTraffic,
665 ((OpticalPathIntent) installable).path().links());
666 }
667 }
668 }
669 }
670 }
671 return biLinks;
672 }
673
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700674 // Extracts links from the specified flow rule intent resources
675 private Collection<Link> linkResources(Intent installable) {
676 ImmutableList.Builder<Link> builder = ImmutableList.builder();
677 for (NetworkResource r : installable.resources()) {
678 if (r instanceof Link) {
679 builder.add((Link) r);
680 }
681 }
682 return builder.build();
683 }
684
Thomas Vachuska329af532015-03-10 02:08:33 -0700685
686 // Adds the link segments (path or tree) associated with the specified
687 // connectivity intent
688 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
689 boolean showTraffic, Iterable<Link> links) {
690 if (links != null) {
691 for (Link link : links) {
692 BiLink biLink = addLink(biLinks, link);
693 if (isInfrastructureEgress(link)) {
694 if (showTraffic) {
695 biLink.addLoad(statService.load(link));
696 }
697 biLink.addClass(type);
698 }
699 }
700 }
701 }
702
703
Thomas Vachuska583bc632015-04-14 10:10:57 -0700704 static BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700705 LinkKey key = canonicalLinkKey(link);
706 BiLink biLink = biLinks.get(key);
707 if (biLink != null) {
708 biLink.setOther(link);
709 } else {
710 biLink = new BiLink(key, link);
711 biLinks.put(key, biLink);
712 }
713 return biLink;
714 }
715
716
717 // Adds the link segments (path or tree) associated with the specified
718 // connectivity intent
719 protected void addPathTraffic(ArrayNode paths, String type, String trafficType,
720 Iterable<Link> links) {
Simon Huntda580882015-05-12 20:58:18 -0700721 ObjectNode pathNode = objectNode();
722 ArrayNode linksNode = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700723
724 if (links != null) {
Simon Huntda580882015-05-12 20:58:18 -0700725 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700726 boolean hasTraffic = false;
727 for (Link link : links) {
728 if (isInfrastructureEgress(link)) {
729 linksNode.add(compactLinkString(link));
730 Load load = statService.load(link);
731 String label = "";
732 if (load.rate() > 0) {
733 hasTraffic = true;
734 label = formatBytes(load.latest());
735 }
736 labels.add(label);
737 }
738 }
739 pathNode.put("class", hasTraffic ? type + " " + trafficType : type);
740 pathNode.put("traffic", hasTraffic);
741 pathNode.set("links", linksNode);
742 pathNode.set("labels", labels);
743 paths.add(pathNode);
744 }
745 }
746
747 // Poor-mans formatting to get the labels with byte counts looking nice.
748 private String formatBytes(long bytes) {
749 String unit;
750 double value;
751 if (bytes > GB) {
752 value = bytes / GB;
753 unit = GB_UNIT;
754 } else if (bytes > MB) {
755 value = bytes / MB;
756 unit = MB_UNIT;
757 } else if (bytes > KB) {
758 value = bytes / KB;
759 unit = KB_UNIT;
760 } else {
761 value = bytes;
762 unit = B_UNIT;
763 }
764 DecimalFormat format = new DecimalFormat("#,###.##");
765 return format.format(value) + " " + unit;
766 }
767
768 // Formats the given number into a string.
769 private String format(Number number) {
770 DecimalFormat format = new DecimalFormat("#,###");
771 return format.format(number);
772 }
773
774 private boolean isInfrastructureEgress(Link link) {
775 return link.src().elementId() instanceof DeviceId;
776 }
777
778 // Produces compact string representation of a link.
779 private static String compactLinkString(Link link) {
780 return String.format(COMPACT, link.src().elementId(), link.src().port(),
781 link.dst().elementId(), link.dst().port());
782 }
783
784 // Produces JSON property details.
785 private ObjectNode json(String id, String type, Prop... props) {
786 ObjectMapper mapper = new ObjectMapper();
Simon Huntda580882015-05-12 20:58:18 -0700787 ObjectNode result = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700788 .put("id", id).put("type", type);
Simon Huntda580882015-05-12 20:58:18 -0700789 ObjectNode pnode = objectNode();
790 ArrayNode porder = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700791 for (Prop p : props) {
792 porder.add(p.key);
793 pnode.put(p.key, p.value);
794 }
795 result.set("propOrder", porder);
796 result.set("props", pnode);
797 return result;
798 }
799
800 // Produces canonical link key, i.e. one that will match link and its inverse.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700801 static LinkKey canonicalLinkKey(Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700802 String sn = link.src().elementId().toString();
803 String dn = link.dst().elementId().toString();
804 return sn.compareTo(dn) < 0 ?
805 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
806 }
807
808 // Representation of link and its inverse and any traffic data.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700809 static class BiLink {
Thomas Vachuska329af532015-03-10 02:08:33 -0700810 public final LinkKey key;
811 public final Link one;
812 public Link two;
813 public boolean hasTraffic = false;
814 public long bytes = 0;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700815
816 private Set<String> classes = new HashSet<>();
Thomas Vachuska329af532015-03-10 02:08:33 -0700817
818 BiLink(LinkKey key, Link link) {
819 this.key = key;
820 this.one = link;
821 }
822
823 void setOther(Link link) {
824 this.two = link;
825 }
826
827 void addLoad(Load load) {
828 if (load != null) {
829 this.hasTraffic = hasTraffic || load.rate() > 0;
830 this.bytes += load.latest();
831 }
832 }
833
834 void addClass(String trafficClass) {
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700835 classes.add(trafficClass);
836 }
837
838 String classes() {
839 StringBuilder sb = new StringBuilder();
840 classes.forEach(c -> sb.append(c).append(" "));
841 return sb.toString().trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700842 }
843 }
844
845 // Auxiliary key/value carrier.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700846 static class Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700847 public final String key;
848 public final String value;
849
850 protected Prop(String key, String value) {
851 this.key = key;
852 this.value = value;
853 }
854 }
855
856 // Auxiliary properties separator
Thomas Vachuska583bc632015-04-14 10:10:57 -0700857 static class Separator extends Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700858 protected Separator() {
859 super("-", "");
860 }
861 }
862
863 // Auxiliary carrier of data for requesting traffic message.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700864 static class TrafficClass {
Thomas Vachuska329af532015-03-10 02:08:33 -0700865 public final boolean showTraffic;
866 public final String type;
867 public final Iterable<Intent> intents;
868
869 TrafficClass(String type, Iterable<Intent> intents) {
870 this(type, intents, false);
871 }
872
873 TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
874 this.type = type;
875 this.intents = intents;
876 this.showTraffic = showTraffic;
877 }
878 }
879
880}