blob: 71eb1fcd7173105b91305f4cdfabfd996beeaf17 [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;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -070030import org.onosproject.incubator.net.PortStatisticsService;
Thomas Vachuska329af532015-03-10 02:08:33 -070031import org.onosproject.mastership.MastershipService;
32import org.onosproject.net.Annotated;
33import org.onosproject.net.AnnotationKeys;
34import org.onosproject.net.Annotations;
35import org.onosproject.net.ConnectPoint;
36import org.onosproject.net.DefaultEdgeLink;
37import org.onosproject.net.Device;
38import org.onosproject.net.DeviceId;
39import org.onosproject.net.EdgeLink;
40import org.onosproject.net.Host;
41import org.onosproject.net.HostId;
42import org.onosproject.net.HostLocation;
43import org.onosproject.net.Link;
44import org.onosproject.net.LinkKey;
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -070045import org.onosproject.net.NetworkResource;
Thomas Vachuska329af532015-03-10 02:08:33 -070046import org.onosproject.net.PortNumber;
47import org.onosproject.net.device.DeviceEvent;
48import org.onosproject.net.device.DeviceService;
49import org.onosproject.net.flow.FlowEntry;
50import org.onosproject.net.flow.FlowRuleService;
51import org.onosproject.net.flow.TrafficTreatment;
52import org.onosproject.net.flow.instructions.Instruction;
53import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
54import org.onosproject.net.host.HostEvent;
55import org.onosproject.net.host.HostService;
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -070056import org.onosproject.net.intent.FlowRuleIntent;
Thomas Vachuska329af532015-03-10 02:08:33 -070057import org.onosproject.net.intent.Intent;
58import org.onosproject.net.intent.IntentService;
59import org.onosproject.net.intent.LinkCollectionIntent;
60import org.onosproject.net.intent.OpticalConnectivityIntent;
61import org.onosproject.net.intent.OpticalPathIntent;
62import org.onosproject.net.intent.PathIntent;
63import org.onosproject.net.link.LinkEvent;
64import org.onosproject.net.link.LinkService;
65import org.onosproject.net.provider.ProviderId;
66import org.onosproject.net.statistic.Load;
67import org.onosproject.net.statistic.StatisticService;
68import org.onosproject.net.topology.Topology;
69import org.onosproject.net.topology.TopologyService;
Simon Huntd2747a02015-04-30 22:41:16 -070070import org.onosproject.ui.JsonUtils;
Thomas Vachuska329af532015-03-10 02:08:33 -070071import org.onosproject.ui.UiConnection;
Simon Hunta0ddb022015-05-01 09:53:01 -070072import org.onosproject.ui.UiMessageHandler;
Thomas Vachuska329af532015-03-10 02:08:33 -070073import org.slf4j.Logger;
74import org.slf4j.LoggerFactory;
75
76import java.text.DecimalFormat;
77import java.util.ArrayList;
78import java.util.Collection;
79import java.util.Collections;
80import java.util.HashMap;
81import java.util.HashSet;
82import java.util.Iterator;
83import java.util.List;
84import java.util.Map;
85import java.util.Set;
86import java.util.concurrent.ConcurrentHashMap;
87
88import static com.google.common.base.Preconditions.checkNotNull;
89import static com.google.common.base.Strings.isNullOrEmpty;
90import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
91import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
92import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
93import static org.onosproject.net.DeviceId.deviceId;
94import static org.onosproject.net.HostId.hostId;
95import static org.onosproject.net.LinkKey.linkKey;
96import static org.onosproject.net.PortNumber.P0;
97import static org.onosproject.net.PortNumber.portNumber;
98import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
99import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
100import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
101import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
102import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
103import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700104import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.*;
Thomas Vachuska329af532015-03-10 02:08:33 -0700105
106/**
107 * Facility for creating messages bound for the topology viewer.
108 */
Simon Huntd7f7bcc2015-05-08 14:13:17 -0700109@Deprecated
Simon Hunta0ddb022015-05-01 09:53:01 -0700110public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
Thomas Vachuska329af532015-03-10 02:08:33 -0700111
Simon Huntd2747a02015-04-30 22:41:16 -0700112 protected static final Logger log =
113 LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700114
Simon Huntd2747a02015-04-30 22:41:16 -0700115 private static final ProviderId PID =
116 new ProviderId("core", "org.onosproject.core", true);
Thomas Vachuska329af532015-03-10 02:08:33 -0700117 private static final String COMPACT = "%s/%s-%s/%s";
118
119 private static final double KB = 1024;
120 private static final double MB = 1024 * KB;
121 private static final double GB = 1024 * MB;
122
123 private static final String GB_UNIT = "GB";
124 private static final String MB_UNIT = "MB";
125 private static final String KB_UNIT = "KB";
126 private static final String B_UNIT = "B";
127
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700128 private static final long BPS_THRESHOLD = 1024;
129
Thomas Vachuska329af532015-03-10 02:08:33 -0700130 protected ServiceDirectory directory;
131 protected ClusterService clusterService;
132 protected DeviceService deviceService;
133 protected LinkService linkService;
134 protected HostService hostService;
135 protected MastershipService mastershipService;
136 protected IntentService intentService;
137 protected FlowRuleService flowService;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700138 protected StatisticService flowStatsService;
139 protected PortStatisticsService portStatsService;
Thomas Vachuska329af532015-03-10 02:08:33 -0700140 protected TopologyService topologyService;
141
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700142 protected enum StatsType {
143 FLOW, PORT
144 }
145
Thomas Vachuska329af532015-03-10 02:08:33 -0700146 private String version;
147
148 // TODO: extract into an external & durable state; good enough for now and demo
149 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
150
151 /**
Thomas Vachuska329af532015-03-10 02:08:33 -0700152 * Returns read-only view of the meta-ui information.
153 *
154 * @return map of id to meta-ui mementos
155 */
156 static Map<String, ObjectNode> getMetaUi() {
157 return Collections.unmodifiableMap(metaUi);
158 }
159
160 @Override
161 public void init(UiConnection connection, ServiceDirectory directory) {
162 super.init(connection, directory);
163 this.directory = checkNotNull(directory, "Directory cannot be null");
164 clusterService = directory.get(ClusterService.class);
165 deviceService = directory.get(DeviceService.class);
166 linkService = directory.get(LinkService.class);
167 hostService = directory.get(HostService.class);
168 mastershipService = directory.get(MastershipService.class);
169 intentService = directory.get(IntentService.class);
170 flowService = directory.get(FlowRuleService.class);
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700171 flowStatsService = directory.get(StatisticService.class);
172 portStatsService = directory.get(PortStatisticsService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700173 topologyService = directory.get(TopologyService.class);
174
175 String ver = directory.get(CoreService.class).version().toString();
176 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
177 }
178
Thomas Vachuska329af532015-03-10 02:08:33 -0700179 // Returns the specified set of IP addresses as a string.
180 private String ip(Set<IpAddress> ipAddresses) {
181 Iterator<IpAddress> it = ipAddresses.iterator();
182 return it.hasNext() ? it.next().toString() : "unknown";
183 }
184
185 // Produces JSON structure from annotations.
186 private JsonNode props(Annotations annotations) {
Simon Huntda580882015-05-12 20:58:18 -0700187 ObjectNode props = objectNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700188 if (annotations != null) {
189 for (String key : annotations.keys()) {
190 props.put(key, annotations.value(key));
191 }
192 }
193 return props;
194 }
195
196 // Produces an informational log message event bound to the client.
197 protected ObjectNode info(long id, String message) {
198 return message("info", id, message);
199 }
200
201 // Produces a warning log message event bound to the client.
202 protected ObjectNode warning(long id, String message) {
203 return message("warning", id, message);
204 }
205
206 // Produces an error log message event bound to the client.
207 protected ObjectNode error(long id, String message) {
208 return message("error", id, message);
209 }
210
211 // Produces a log message event bound to the client.
212 private ObjectNode message(String severity, long id, String message) {
Simon Huntda580882015-05-12 20:58:18 -0700213 ObjectNode payload = objectNode()
Simon Huntd2747a02015-04-30 22:41:16 -0700214 .put("severity", severity)
215 .put("message", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700216
Simon Huntd2747a02015-04-30 22:41:16 -0700217 return JsonUtils.envelope("message", id, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700218 }
219
220 // Produces a set of all hosts listed in the specified JSON array.
221 protected Set<Host> getHosts(ArrayNode array) {
222 Set<Host> hosts = new HashSet<>();
223 if (array != null) {
224 for (JsonNode node : array) {
225 try {
226 addHost(hosts, hostId(node.asText()));
227 } catch (IllegalArgumentException e) {
228 log.debug("Skipping ID {}", node.asText());
229 }
230 }
231 }
232 return hosts;
233 }
234
235 // Adds the specified host to the set of hosts.
236 private void addHost(Set<Host> hosts, HostId hostId) {
237 Host host = hostService.getHost(hostId);
238 if (host != null) {
239 hosts.add(host);
240 }
241 }
242
243
244 // Produces a set of all devices listed in the specified JSON array.
245 protected Set<Device> getDevices(ArrayNode array) {
246 Set<Device> devices = new HashSet<>();
247 if (array != null) {
248 for (JsonNode node : array) {
249 try {
250 addDevice(devices, deviceId(node.asText()));
251 } catch (IllegalArgumentException e) {
252 log.debug("Skipping ID {}", node.asText());
253 }
254 }
255 }
256 return devices;
257 }
258
259 private void addDevice(Set<Device> devices, DeviceId deviceId) {
260 Device device = deviceService.getDevice(deviceId);
261 if (device != null) {
262 devices.add(device);
263 }
264 }
265
266 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
267 try {
268 addHost(hosts, hostId(hover));
269 } catch (IllegalArgumentException e) {
270 try {
271 addDevice(devices, deviceId(hover));
272 } catch (IllegalArgumentException ne) {
273 log.debug("Skipping ID {}", hover);
274 }
275 }
276 }
277
278 // Produces a cluster instance message to the client.
279 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
280 ControllerNode node = event.subject();
281 int switchCount = mastershipService.getDevicesOf(node.id()).size();
Simon Huntda580882015-05-12 20:58:18 -0700282 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700283 .put("id", node.id().toString())
284 .put("ip", node.ip().toString())
285 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700286 .put("uiAttached", node.equals(clusterService.getLocalNode()))
Thomas Vachuska329af532015-03-10 02:08:33 -0700287 .put("switches", switchCount);
288
Simon Huntda580882015-05-12 20:58:18 -0700289 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700290 labels.add(node.id().toString());
291 labels.add(node.ip().toString());
292
293 // Add labels, props and stuff the payload into envelope.
294 payload.set("labels", labels);
295 addMetaUi(node.id().toString(), payload);
296
297 String type = messageType != null ? messageType :
298 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
299 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
300 "addInstance")));
Simon Huntd2747a02015-04-30 22:41:16 -0700301 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700302 }
303
304 // Produces a device event message to the client.
305 protected ObjectNode deviceMessage(DeviceEvent event) {
306 Device device = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700307 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700308 .put("id", device.id().toString())
309 .put("type", device.type().toString().toLowerCase())
310 .put("online", deviceService.isAvailable(device.id()))
311 .put("master", master(device.id()));
312
313 // Generate labels: id, chassis id, no-label, optional-name
314 String name = device.annotations().value(AnnotationKeys.NAME);
Simon Huntda580882015-05-12 20:58:18 -0700315 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700316 labels.add("");
317 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
318 labels.add(device.id().toString());
319
320 // Add labels, props and stuff the payload into envelope.
321 payload.set("labels", labels);
322 payload.set("props", props(device.annotations()));
323 addGeoLocation(device, payload);
324 addMetaUi(device.id().toString(), payload);
325
326 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
327 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
Simon Huntd2747a02015-04-30 22:41:16 -0700328 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700329 }
330
331 // Produces a link event message to the client.
332 protected ObjectNode linkMessage(LinkEvent event) {
333 Link link = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700334 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700335 .put("id", compactLinkString(link))
336 .put("type", link.type().toString().toLowerCase())
337 .put("online", link.state() == Link.State.ACTIVE)
338 .put("linkWidth", 1.2)
339 .put("src", link.src().deviceId().toString())
340 .put("srcPort", link.src().port().toString())
341 .put("dst", link.dst().deviceId().toString())
342 .put("dstPort", link.dst().port().toString());
343 String type = (event.type() == LINK_ADDED) ? "addLink" :
344 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
Simon Huntd2747a02015-04-30 22:41:16 -0700345 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700346 }
347
348 // Produces a host event message to the client.
349 protected ObjectNode hostMessage(HostEvent event) {
350 Host host = event.subject();
351 String hostType = host.annotations().value(AnnotationKeys.TYPE);
Simon Huntda580882015-05-12 20:58:18 -0700352 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700353 .put("id", host.id().toString())
354 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
355 .put("ingress", compactLinkString(edgeLink(host, true)))
356 .put("egress", compactLinkString(edgeLink(host, false)));
Simon Huntda580882015-05-12 20:58:18 -0700357 payload.set("cp", hostConnect(host.location()));
358 payload.set("labels", labels(ip(host.ipAddresses()),
Thomas Vachuska329af532015-03-10 02:08:33 -0700359 host.mac().toString()));
360 payload.set("props", props(host.annotations()));
361 addGeoLocation(host, payload);
362 addMetaUi(host.id().toString(), payload);
363
364 String type = (event.type() == HOST_ADDED) ? "addHost" :
365 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
Simon Huntd2747a02015-04-30 22:41:16 -0700366 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700367 }
368
369 // Encodes the specified host location into a JSON object.
Simon Huntda580882015-05-12 20:58:18 -0700370 private ObjectNode hostConnect(HostLocation location) {
371 return objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700372 .put("device", location.deviceId().toString())
373 .put("port", location.port().toLong());
374 }
375
376 // Encodes the specified list of labels a JSON array.
Simon Huntda580882015-05-12 20:58:18 -0700377 private ArrayNode labels(String... labels) {
378 ArrayNode json = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700379 for (String label : labels) {
380 json.add(label);
381 }
382 return json;
383 }
384
385 // Returns the name of the master node for the specified device id.
386 private String master(DeviceId deviceId) {
387 NodeId master = mastershipService.getMasterFor(deviceId);
388 return master != null ? master.toString() : "";
389 }
390
391 // Generates an edge link from the specified host location.
392 private EdgeLink edgeLink(Host host, boolean ingress) {
393 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
394 host.location(), ingress);
395 }
396
397 // Adds meta UI information for the specified object.
398 private void addMetaUi(String id, ObjectNode payload) {
399 ObjectNode meta = metaUi.get(id);
400 if (meta != null) {
401 payload.set("metaUi", meta);
402 }
403 }
404
405 // Adds a geo location JSON to the specified payload object.
406 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
407 Annotations annotations = annotated.annotations();
408 if (annotations == null) {
409 return;
410 }
411
412 String slat = annotations.value(AnnotationKeys.LATITUDE);
413 String slng = annotations.value(AnnotationKeys.LONGITUDE);
414 try {
415 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
416 double lat = Double.parseDouble(slat);
417 double lng = Double.parseDouble(slng);
Simon Huntda580882015-05-12 20:58:18 -0700418 ObjectNode loc = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700419 .put("type", "latlng").put("lat", lat).put("lng", lng);
420 payload.set("location", loc);
421 }
422 } catch (NumberFormatException e) {
423 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
424 }
425 }
426
427 // Updates meta UI information for the specified object.
Simon Huntd2747a02015-04-30 22:41:16 -0700428 protected void updateMetaUi(ObjectNode payload) {
429 metaUi.put(JsonUtils.string(payload, "id"),
430 JsonUtils.node(payload, "memento"));
Thomas Vachuska329af532015-03-10 02:08:33 -0700431 }
432
433 // Returns summary response.
434 protected ObjectNode summmaryMessage(long sid) {
435 Topology topology = topologyService.currentTopology();
Simon Huntd2747a02015-04-30 22:41:16 -0700436 return JsonUtils.envelope("showSummary", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700437 json("ONOS Summary", "node",
438 new Prop("Devices", format(topology.deviceCount())),
439 new Prop("Links", format(topology.linkCount())),
440 new Prop("Hosts", format(hostService.getHostCount())),
441 new Prop("Topology SCCs", format(topology.clusterCount())),
442 new Separator(),
443 new Prop("Intents", format(intentService.getIntentCount())),
444 new Prop("Flows", format(flowService.getFlowRuleCount())),
445 new Prop("Version", version)));
Thomas Vachuska329af532015-03-10 02:08:33 -0700446 }
447
448 // Returns device details response.
449 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
450 Device device = deviceService.getDevice(deviceId);
451 Annotations annot = device.annotations();
452 String name = annot.value(AnnotationKeys.NAME);
453 int portCount = deviceService.getPorts(deviceId).size();
454 int flowCount = getFlowCount(deviceId);
Simon Huntd2747a02015-04-30 22:41:16 -0700455 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700456 json(isNullOrEmpty(name) ? deviceId.toString() : name,
457 device.type().toString().toLowerCase(),
458 new Prop("URI", deviceId.toString()),
459 new Prop("Vendor", device.manufacturer()),
460 new Prop("H/W Version", device.hwVersion()),
461 new Prop("S/W Version", device.swVersion()),
462 new Prop("Serial Number", device.serialNumber()),
463 new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
464 new Separator(),
465 new Prop("Master", master(deviceId)),
466 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
467 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
468 new Separator(),
469 new Prop("Ports", Integer.toString(portCount)),
470 new Prop("Flows", Integer.toString(flowCount))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700471 }
472
473 protected int getFlowCount(DeviceId deviceId) {
474 int count = 0;
475 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
476 while (it.hasNext()) {
477 count++;
478 it.next();
479 }
480 return count;
481 }
482
483 // Counts all entries that egress on the given device links.
484 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
485 List<FlowEntry> entries = new ArrayList<>();
486 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
487 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
488 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
489 while (it.hasNext()) {
490 entries.add(it.next());
491 }
492
493 // Add all edge links to the set
494 if (hosts != null) {
495 for (Host host : hosts) {
496 links.add(new DefaultEdgeLink(host.providerId(),
497 new ConnectPoint(host.id(), P0),
498 host.location(), false));
499 }
500 }
501
502 Map<Link, Integer> counts = new HashMap<>();
503 for (Link link : links) {
504 counts.put(link, getEgressFlows(link, entries));
505 }
506 return counts;
507 }
508
509 // Counts all entries that egress on the link source port.
510 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
511 int count = 0;
512 PortNumber out = link.src().port();
513 for (FlowEntry entry : entries) {
514 TrafficTreatment treatment = entry.treatment();
Ray Milkey42507352015-03-20 15:16:10 -0700515 for (Instruction instruction : treatment.allInstructions()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700516 if (instruction.type() == Instruction.Type.OUTPUT &&
517 ((OutputInstruction) instruction).port().equals(out)) {
518 count++;
519 }
520 }
521 }
522 return count;
523 }
524
525
526 // Returns host details response.
527 protected ObjectNode hostDetails(HostId hostId, long sid) {
528 Host host = hostService.getHost(hostId);
529 Annotations annot = host.annotations();
530 String type = annot.value(AnnotationKeys.TYPE);
531 String name = annot.value(AnnotationKeys.NAME);
532 String vlan = host.vlan().toString();
Simon Huntd2747a02015-04-30 22:41:16 -0700533 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700534 json(isNullOrEmpty(name) ? hostId.toString() : name,
535 isNullOrEmpty(type) ? "endstation" : type,
536 new Prop("MAC", host.mac().toString()),
537 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
538 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
539 new Separator(),
540 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
541 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700542 }
543
544
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700545 // Produces JSON message to trigger flow traffic overview visualization
546 protected ObjectNode trafficSummaryMessage(StatsType type) {
Simon Huntda580882015-05-12 20:58:18 -0700547 ObjectNode payload = objectNode();
548 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700549 payload.set("paths", paths);
550
Simon Huntda580882015-05-12 20:58:18 -0700551 ObjectNode pathNodeN = objectNode();
552 ArrayNode linksNodeN = arrayNode();
553 ArrayNode labelsN = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700554
555 pathNodeN.put("class", "plain").put("traffic", false);
556 pathNodeN.set("links", linksNodeN);
557 pathNodeN.set("labels", labelsN);
558 paths.add(pathNodeN);
559
Simon Huntda580882015-05-12 20:58:18 -0700560 ObjectNode pathNodeT = objectNode();
561 ArrayNode linksNodeT = arrayNode();
562 ArrayNode labelsT = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700563
564 pathNodeT.put("class", "secondary").put("traffic", true);
565 pathNodeT.set("links", linksNodeT);
566 pathNodeT.set("labels", labelsT);
567 paths.add(pathNodeT);
568
569 for (BiLink link : consolidateLinks(linkService.getLinks())) {
570 boolean bi = link.two != null;
571 if (isInfrastructureEgress(link.one) ||
572 (bi && isInfrastructureEgress(link.two))) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700573 if (type == FLOW) {
574 link.addLoad(flowStatsService.load(link.one));
575 link.addLoad(bi ? flowStatsService.load(link.two) : null);
576 } else if (type == PORT) {
577 link.addLoad(portStatsService.load(link.one.src()), BPS_THRESHOLD);
578 link.addLoad(bi ? portStatsService.load(link.two.src()) : null, BPS_THRESHOLD);
579 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700580 if (link.hasTraffic) {
581 linksNodeT.add(compactLinkString(link.one));
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700582 labelsT.add(type == PORT ?
583 formatBytes(link.rate) + "ps" :
584 formatBytes(link.bytes));
Thomas Vachuska329af532015-03-10 02:08:33 -0700585 } else {
586 linksNodeN.add(compactLinkString(link.one));
587 labelsN.add("");
588 }
589 }
590 }
Simon Huntd2747a02015-04-30 22:41:16 -0700591 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700592 }
593
594 private Collection<BiLink> consolidateLinks(Iterable<Link> links) {
595 Map<LinkKey, BiLink> biLinks = new HashMap<>();
596 for (Link link : links) {
597 addLink(biLinks, link);
598 }
599 return biLinks.values();
600 }
601
602 // Produces JSON message to trigger flow overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700603 protected ObjectNode flowSummaryMessage(Set<Device> devices) {
Simon Huntda580882015-05-12 20:58:18 -0700604 ObjectNode payload = objectNode();
605 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700606 payload.set("paths", paths);
607
608 for (Device device : devices) {
609 Map<Link, Integer> counts = getFlowCounts(device.id());
610 for (Link link : counts.keySet()) {
611 addLinkFlows(link, paths, counts.get(link));
612 }
613 }
Simon Huntd2747a02015-04-30 22:41:16 -0700614 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700615 }
616
617 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
Simon Huntda580882015-05-12 20:58:18 -0700618 ObjectNode pathNode = objectNode();
619 ArrayNode linksNode = arrayNode();
620 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700621 boolean noFlows = count == null || count == 0;
622 pathNode.put("class", noFlows ? "secondary" : "primary");
623 pathNode.put("traffic", false);
624 pathNode.set("links", linksNode.add(compactLinkString(link)));
625 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
626 (count == 1 ? " flow" : " flows"))));
627 paths.add(pathNode);
628 }
629
630
631 // Produces JSON message to trigger traffic visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700632 protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
Simon Huntda580882015-05-12 20:58:18 -0700633 ObjectNode payload = objectNode();
634 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700635 payload.set("paths", paths);
636
637 // Classify links based on their traffic traffic first...
638 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
639
640 // Then separate the links into their respective classes and send them out.
641 Map<String, ObjectNode> pathNodes = new HashMap<>();
642 for (BiLink biLink : biLinks.values()) {
643 boolean hasTraffic = biLink.hasTraffic;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700644 String tc = (biLink.classes() + (hasTraffic ? " animated" : "")).trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700645 ObjectNode pathNode = pathNodes.get(tc);
646 if (pathNode == null) {
Simon Huntda580882015-05-12 20:58:18 -0700647 pathNode = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700648 .put("class", tc).put("traffic", hasTraffic);
Simon Huntda580882015-05-12 20:58:18 -0700649 pathNode.set("links", arrayNode());
650 pathNode.set("labels", arrayNode());
Thomas Vachuska329af532015-03-10 02:08:33 -0700651 pathNodes.put(tc, pathNode);
652 paths.add(pathNode);
653 }
654 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
655 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
656 }
657
Simon Huntd2747a02015-04-30 22:41:16 -0700658 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700659 }
660
661 // Classifies the link traffic according to the specified classes.
662 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
663 Map<LinkKey, BiLink> biLinks = new HashMap<>();
664 for (TrafficClass trafficClass : trafficClasses) {
665 for (Intent intent : trafficClass.intents) {
666 boolean isOptical = intent instanceof OpticalConnectivityIntent;
667 List<Intent> installables = intentService.getInstallableIntents(intent.key());
668 if (installables != null) {
669 for (Intent installable : installables) {
670 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
671 if (installable instanceof PathIntent) {
672 classifyLinks(type, biLinks, trafficClass.showTraffic,
673 ((PathIntent) installable).path().links());
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700674 } else if (installable instanceof FlowRuleIntent) {
675 classifyLinks(type, biLinks, trafficClass.showTraffic,
676 linkResources(installable));
Thomas Vachuska329af532015-03-10 02:08:33 -0700677 } else if (installable instanceof LinkCollectionIntent) {
678 classifyLinks(type, biLinks, trafficClass.showTraffic,
679 ((LinkCollectionIntent) installable).links());
680 } else if (installable instanceof OpticalPathIntent) {
681 classifyLinks(type, biLinks, trafficClass.showTraffic,
682 ((OpticalPathIntent) installable).path().links());
683 }
684 }
685 }
686 }
687 }
688 return biLinks;
689 }
690
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700691 // Extracts links from the specified flow rule intent resources
692 private Collection<Link> linkResources(Intent installable) {
693 ImmutableList.Builder<Link> builder = ImmutableList.builder();
694 for (NetworkResource r : installable.resources()) {
695 if (r instanceof Link) {
696 builder.add((Link) r);
697 }
698 }
699 return builder.build();
700 }
701
Thomas Vachuska329af532015-03-10 02:08:33 -0700702
703 // Adds the link segments (path or tree) associated with the specified
704 // connectivity intent
705 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
706 boolean showTraffic, Iterable<Link> links) {
707 if (links != null) {
708 for (Link link : links) {
709 BiLink biLink = addLink(biLinks, link);
710 if (isInfrastructureEgress(link)) {
711 if (showTraffic) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700712 biLink.addLoad(flowStatsService.load(link));
Thomas Vachuska329af532015-03-10 02:08:33 -0700713 }
714 biLink.addClass(type);
715 }
716 }
717 }
718 }
719
720
Thomas Vachuska583bc632015-04-14 10:10:57 -0700721 static BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700722 LinkKey key = canonicalLinkKey(link);
723 BiLink biLink = biLinks.get(key);
724 if (biLink != null) {
725 biLink.setOther(link);
726 } else {
727 biLink = new BiLink(key, link);
728 biLinks.put(key, biLink);
729 }
730 return biLink;
731 }
732
733
734 // Adds the link segments (path or tree) associated with the specified
735 // connectivity intent
736 protected void addPathTraffic(ArrayNode paths, String type, String trafficType,
737 Iterable<Link> links) {
Simon Huntda580882015-05-12 20:58:18 -0700738 ObjectNode pathNode = objectNode();
739 ArrayNode linksNode = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700740
741 if (links != null) {
Simon Huntda580882015-05-12 20:58:18 -0700742 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700743 boolean hasTraffic = false;
744 for (Link link : links) {
745 if (isInfrastructureEgress(link)) {
746 linksNode.add(compactLinkString(link));
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700747 Load load = flowStatsService.load(link);
Thomas Vachuska329af532015-03-10 02:08:33 -0700748 String label = "";
749 if (load.rate() > 0) {
750 hasTraffic = true;
751 label = formatBytes(load.latest());
752 }
753 labels.add(label);
754 }
755 }
756 pathNode.put("class", hasTraffic ? type + " " + trafficType : type);
757 pathNode.put("traffic", hasTraffic);
758 pathNode.set("links", linksNode);
759 pathNode.set("labels", labels);
760 paths.add(pathNode);
761 }
762 }
763
764 // Poor-mans formatting to get the labels with byte counts looking nice.
765 private String formatBytes(long bytes) {
766 String unit;
767 double value;
768 if (bytes > GB) {
769 value = bytes / GB;
770 unit = GB_UNIT;
771 } else if (bytes > MB) {
772 value = bytes / MB;
773 unit = MB_UNIT;
774 } else if (bytes > KB) {
775 value = bytes / KB;
776 unit = KB_UNIT;
777 } else {
778 value = bytes;
779 unit = B_UNIT;
780 }
781 DecimalFormat format = new DecimalFormat("#,###.##");
782 return format.format(value) + " " + unit;
783 }
784
785 // Formats the given number into a string.
786 private String format(Number number) {
787 DecimalFormat format = new DecimalFormat("#,###");
788 return format.format(number);
789 }
790
791 private boolean isInfrastructureEgress(Link link) {
792 return link.src().elementId() instanceof DeviceId;
793 }
794
795 // Produces compact string representation of a link.
796 private static String compactLinkString(Link link) {
797 return String.format(COMPACT, link.src().elementId(), link.src().port(),
798 link.dst().elementId(), link.dst().port());
799 }
800
801 // Produces JSON property details.
802 private ObjectNode json(String id, String type, Prop... props) {
803 ObjectMapper mapper = new ObjectMapper();
Simon Huntda580882015-05-12 20:58:18 -0700804 ObjectNode result = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700805 .put("id", id).put("type", type);
Simon Huntda580882015-05-12 20:58:18 -0700806 ObjectNode pnode = objectNode();
807 ArrayNode porder = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700808 for (Prop p : props) {
809 porder.add(p.key);
810 pnode.put(p.key, p.value);
811 }
812 result.set("propOrder", porder);
813 result.set("props", pnode);
814 return result;
815 }
816
817 // Produces canonical link key, i.e. one that will match link and its inverse.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700818 static LinkKey canonicalLinkKey(Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700819 String sn = link.src().elementId().toString();
820 String dn = link.dst().elementId().toString();
821 return sn.compareTo(dn) < 0 ?
822 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
823 }
824
825 // Representation of link and its inverse and any traffic data.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700826 static class BiLink {
Thomas Vachuska329af532015-03-10 02:08:33 -0700827 public final LinkKey key;
828 public final Link one;
829 public Link two;
830 public boolean hasTraffic = false;
831 public long bytes = 0;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700832
833 private Set<String> classes = new HashSet<>();
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700834 private long rate;
Thomas Vachuska329af532015-03-10 02:08:33 -0700835
836 BiLink(LinkKey key, Link link) {
837 this.key = key;
838 this.one = link;
839 }
840
841 void setOther(Link link) {
842 this.two = link;
843 }
844
845 void addLoad(Load load) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700846 addLoad(load, 0);
847 }
848
849 void addLoad(Load load, long threshold) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700850 if (load != null) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700851 this.hasTraffic = hasTraffic || load.rate() > threshold;
Thomas Vachuska329af532015-03-10 02:08:33 -0700852 this.bytes += load.latest();
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700853 this.rate = load.rate();
Thomas Vachuska329af532015-03-10 02:08:33 -0700854 }
855 }
856
857 void addClass(String trafficClass) {
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700858 classes.add(trafficClass);
859 }
860
861 String classes() {
862 StringBuilder sb = new StringBuilder();
863 classes.forEach(c -> sb.append(c).append(" "));
864 return sb.toString().trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700865 }
866 }
867
868 // Auxiliary key/value carrier.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700869 static class Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700870 public final String key;
871 public final String value;
872
873 protected Prop(String key, String value) {
874 this.key = key;
875 this.value = value;
876 }
877 }
878
879 // Auxiliary properties separator
Thomas Vachuska583bc632015-04-14 10:10:57 -0700880 static class Separator extends Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700881 protected Separator() {
882 super("-", "");
883 }
884 }
885
886 // Auxiliary carrier of data for requesting traffic message.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700887 static class TrafficClass {
Thomas Vachuska329af532015-03-10 02:08:33 -0700888 public final boolean showTraffic;
889 public final String type;
890 public final Iterable<Intent> intents;
891
892 TrafficClass(String type, Iterable<Intent> intents) {
893 this(type, intents, false);
894 }
895
896 TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
897 this.type = type;
898 this.intents = intents;
899 this.showTraffic = showTraffic;
900 }
901 }
902
903}