blob: b006b1eb83d04d79664b089c24170600c080c41e [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
Simon Hunt96641372015-06-02 15:53:25 -0700123 // TODO: change GB to Gb (when we compute bits/second)
Thomas Vachuska329af532015-03-10 02:08:33 -0700124 private static final String GB_UNIT = "GB";
125 private static final String MB_UNIT = "MB";
126 private static final String KB_UNIT = "KB";
127 private static final String B_UNIT = "B";
128
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700129 private static final long BPS_THRESHOLD = 1024;
130
Thomas Vachuska329af532015-03-10 02:08:33 -0700131 protected ServiceDirectory directory;
132 protected ClusterService clusterService;
133 protected DeviceService deviceService;
134 protected LinkService linkService;
135 protected HostService hostService;
136 protected MastershipService mastershipService;
137 protected IntentService intentService;
138 protected FlowRuleService flowService;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700139 protected StatisticService flowStatsService;
140 protected PortStatisticsService portStatsService;
Thomas Vachuska329af532015-03-10 02:08:33 -0700141 protected TopologyService topologyService;
142
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700143 protected enum StatsType {
144 FLOW, PORT
145 }
146
Thomas Vachuska329af532015-03-10 02:08:33 -0700147 private String version;
148
149 // TODO: extract into an external & durable state; good enough for now and demo
150 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
151
152 /**
Thomas Vachuska329af532015-03-10 02:08:33 -0700153 * Returns read-only view of the meta-ui information.
154 *
155 * @return map of id to meta-ui mementos
156 */
157 static Map<String, ObjectNode> getMetaUi() {
158 return Collections.unmodifiableMap(metaUi);
159 }
160
161 @Override
162 public void init(UiConnection connection, ServiceDirectory directory) {
163 super.init(connection, directory);
164 this.directory = checkNotNull(directory, "Directory cannot be null");
165 clusterService = directory.get(ClusterService.class);
166 deviceService = directory.get(DeviceService.class);
167 linkService = directory.get(LinkService.class);
168 hostService = directory.get(HostService.class);
169 mastershipService = directory.get(MastershipService.class);
170 intentService = directory.get(IntentService.class);
171 flowService = directory.get(FlowRuleService.class);
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700172 flowStatsService = directory.get(StatisticService.class);
173 portStatsService = directory.get(PortStatisticsService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700174 topologyService = directory.get(TopologyService.class);
175
176 String ver = directory.get(CoreService.class).version().toString();
177 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
178 }
179
Thomas Vachuska329af532015-03-10 02:08:33 -0700180 // Returns the specified set of IP addresses as a string.
181 private String ip(Set<IpAddress> ipAddresses) {
182 Iterator<IpAddress> it = ipAddresses.iterator();
183 return it.hasNext() ? it.next().toString() : "unknown";
184 }
185
186 // Produces JSON structure from annotations.
187 private JsonNode props(Annotations annotations) {
Simon Huntda580882015-05-12 20:58:18 -0700188 ObjectNode props = objectNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700189 if (annotations != null) {
190 for (String key : annotations.keys()) {
191 props.put(key, annotations.value(key));
192 }
193 }
194 return props;
195 }
196
197 // Produces an informational log message event bound to the client.
198 protected ObjectNode info(long id, String message) {
199 return message("info", id, message);
200 }
201
202 // Produces a warning log message event bound to the client.
203 protected ObjectNode warning(long id, String message) {
204 return message("warning", id, message);
205 }
206
207 // Produces an error log message event bound to the client.
208 protected ObjectNode error(long id, String message) {
209 return message("error", id, message);
210 }
211
212 // Produces a log message event bound to the client.
213 private ObjectNode message(String severity, long id, String message) {
Simon Huntda580882015-05-12 20:58:18 -0700214 ObjectNode payload = objectNode()
Simon Huntd2747a02015-04-30 22:41:16 -0700215 .put("severity", severity)
216 .put("message", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700217
Simon Huntd2747a02015-04-30 22:41:16 -0700218 return JsonUtils.envelope("message", id, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700219 }
220
221 // Produces a set of all hosts listed in the specified JSON array.
222 protected Set<Host> getHosts(ArrayNode array) {
223 Set<Host> hosts = new HashSet<>();
224 if (array != null) {
225 for (JsonNode node : array) {
226 try {
227 addHost(hosts, hostId(node.asText()));
228 } catch (IllegalArgumentException e) {
229 log.debug("Skipping ID {}", node.asText());
230 }
231 }
232 }
233 return hosts;
234 }
235
236 // Adds the specified host to the set of hosts.
237 private void addHost(Set<Host> hosts, HostId hostId) {
238 Host host = hostService.getHost(hostId);
239 if (host != null) {
240 hosts.add(host);
241 }
242 }
243
244
245 // Produces a set of all devices listed in the specified JSON array.
246 protected Set<Device> getDevices(ArrayNode array) {
247 Set<Device> devices = new HashSet<>();
248 if (array != null) {
249 for (JsonNode node : array) {
250 try {
251 addDevice(devices, deviceId(node.asText()));
252 } catch (IllegalArgumentException e) {
253 log.debug("Skipping ID {}", node.asText());
254 }
255 }
256 }
257 return devices;
258 }
259
260 private void addDevice(Set<Device> devices, DeviceId deviceId) {
261 Device device = deviceService.getDevice(deviceId);
262 if (device != null) {
263 devices.add(device);
264 }
265 }
266
267 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
268 try {
269 addHost(hosts, hostId(hover));
270 } catch (IllegalArgumentException e) {
271 try {
272 addDevice(devices, deviceId(hover));
273 } catch (IllegalArgumentException ne) {
274 log.debug("Skipping ID {}", hover);
275 }
276 }
277 }
278
279 // Produces a cluster instance message to the client.
280 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
281 ControllerNode node = event.subject();
282 int switchCount = mastershipService.getDevicesOf(node.id()).size();
Simon Huntda580882015-05-12 20:58:18 -0700283 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700284 .put("id", node.id().toString())
285 .put("ip", node.ip().toString())
286 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700287 .put("uiAttached", node.equals(clusterService.getLocalNode()))
Thomas Vachuska329af532015-03-10 02:08:33 -0700288 .put("switches", switchCount);
289
Simon Huntda580882015-05-12 20:58:18 -0700290 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700291 labels.add(node.id().toString());
292 labels.add(node.ip().toString());
293
294 // Add labels, props and stuff the payload into envelope.
295 payload.set("labels", labels);
296 addMetaUi(node.id().toString(), payload);
297
298 String type = messageType != null ? messageType :
299 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
300 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
301 "addInstance")));
Simon Huntd2747a02015-04-30 22:41:16 -0700302 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700303 }
304
305 // Produces a device event message to the client.
306 protected ObjectNode deviceMessage(DeviceEvent event) {
307 Device device = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700308 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700309 .put("id", device.id().toString())
310 .put("type", device.type().toString().toLowerCase())
311 .put("online", deviceService.isAvailable(device.id()))
312 .put("master", master(device.id()));
313
314 // Generate labels: id, chassis id, no-label, optional-name
315 String name = device.annotations().value(AnnotationKeys.NAME);
Simon Huntda580882015-05-12 20:58:18 -0700316 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700317 labels.add("");
318 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
319 labels.add(device.id().toString());
320
321 // Add labels, props and stuff the payload into envelope.
322 payload.set("labels", labels);
323 payload.set("props", props(device.annotations()));
324 addGeoLocation(device, payload);
325 addMetaUi(device.id().toString(), payload);
326
327 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
328 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
Simon Huntd2747a02015-04-30 22:41:16 -0700329 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700330 }
331
332 // Produces a link event message to the client.
333 protected ObjectNode linkMessage(LinkEvent event) {
334 Link link = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700335 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700336 .put("id", compactLinkString(link))
337 .put("type", link.type().toString().toLowerCase())
338 .put("online", link.state() == Link.State.ACTIVE)
339 .put("linkWidth", 1.2)
340 .put("src", link.src().deviceId().toString())
341 .put("srcPort", link.src().port().toString())
342 .put("dst", link.dst().deviceId().toString())
343 .put("dstPort", link.dst().port().toString());
344 String type = (event.type() == LINK_ADDED) ? "addLink" :
345 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
Simon Huntd2747a02015-04-30 22:41:16 -0700346 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700347 }
348
349 // Produces a host event message to the client.
350 protected ObjectNode hostMessage(HostEvent event) {
351 Host host = event.subject();
352 String hostType = host.annotations().value(AnnotationKeys.TYPE);
Simon Huntda580882015-05-12 20:58:18 -0700353 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700354 .put("id", host.id().toString())
355 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
356 .put("ingress", compactLinkString(edgeLink(host, true)))
357 .put("egress", compactLinkString(edgeLink(host, false)));
Simon Huntda580882015-05-12 20:58:18 -0700358 payload.set("cp", hostConnect(host.location()));
359 payload.set("labels", labels(ip(host.ipAddresses()),
Thomas Vachuska329af532015-03-10 02:08:33 -0700360 host.mac().toString()));
361 payload.set("props", props(host.annotations()));
362 addGeoLocation(host, payload);
363 addMetaUi(host.id().toString(), payload);
364
365 String type = (event.type() == HOST_ADDED) ? "addHost" :
366 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
Simon Huntd2747a02015-04-30 22:41:16 -0700367 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700368 }
369
370 // Encodes the specified host location into a JSON object.
Simon Huntda580882015-05-12 20:58:18 -0700371 private ObjectNode hostConnect(HostLocation location) {
372 return objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700373 .put("device", location.deviceId().toString())
374 .put("port", location.port().toLong());
375 }
376
377 // Encodes the specified list of labels a JSON array.
Simon Huntda580882015-05-12 20:58:18 -0700378 private ArrayNode labels(String... labels) {
379 ArrayNode json = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700380 for (String label : labels) {
381 json.add(label);
382 }
383 return json;
384 }
385
386 // Returns the name of the master node for the specified device id.
387 private String master(DeviceId deviceId) {
388 NodeId master = mastershipService.getMasterFor(deviceId);
389 return master != null ? master.toString() : "";
390 }
391
392 // Generates an edge link from the specified host location.
393 private EdgeLink edgeLink(Host host, boolean ingress) {
394 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
395 host.location(), ingress);
396 }
397
398 // Adds meta UI information for the specified object.
399 private void addMetaUi(String id, ObjectNode payload) {
400 ObjectNode meta = metaUi.get(id);
401 if (meta != null) {
402 payload.set("metaUi", meta);
403 }
404 }
405
406 // Adds a geo location JSON to the specified payload object.
407 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
408 Annotations annotations = annotated.annotations();
409 if (annotations == null) {
410 return;
411 }
412
413 String slat = annotations.value(AnnotationKeys.LATITUDE);
414 String slng = annotations.value(AnnotationKeys.LONGITUDE);
415 try {
416 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
417 double lat = Double.parseDouble(slat);
418 double lng = Double.parseDouble(slng);
Simon Huntda580882015-05-12 20:58:18 -0700419 ObjectNode loc = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700420 .put("type", "latlng").put("lat", lat).put("lng", lng);
421 payload.set("location", loc);
422 }
423 } catch (NumberFormatException e) {
424 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
425 }
426 }
427
428 // Updates meta UI information for the specified object.
Simon Huntd2747a02015-04-30 22:41:16 -0700429 protected void updateMetaUi(ObjectNode payload) {
430 metaUi.put(JsonUtils.string(payload, "id"),
431 JsonUtils.node(payload, "memento"));
Thomas Vachuska329af532015-03-10 02:08:33 -0700432 }
433
434 // Returns summary response.
435 protected ObjectNode summmaryMessage(long sid) {
436 Topology topology = topologyService.currentTopology();
Simon Huntd2747a02015-04-30 22:41:16 -0700437 return JsonUtils.envelope("showSummary", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700438 json("ONOS Summary", "node",
439 new Prop("Devices", format(topology.deviceCount())),
440 new Prop("Links", format(topology.linkCount())),
441 new Prop("Hosts", format(hostService.getHostCount())),
442 new Prop("Topology SCCs", format(topology.clusterCount())),
443 new Separator(),
444 new Prop("Intents", format(intentService.getIntentCount())),
445 new Prop("Flows", format(flowService.getFlowRuleCount())),
446 new Prop("Version", version)));
Thomas Vachuska329af532015-03-10 02:08:33 -0700447 }
448
449 // Returns device details response.
450 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
451 Device device = deviceService.getDevice(deviceId);
452 Annotations annot = device.annotations();
453 String name = annot.value(AnnotationKeys.NAME);
454 int portCount = deviceService.getPorts(deviceId).size();
455 int flowCount = getFlowCount(deviceId);
Simon Huntd2747a02015-04-30 22:41:16 -0700456 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700457 json(isNullOrEmpty(name) ? deviceId.toString() : name,
458 device.type().toString().toLowerCase(),
459 new Prop("URI", deviceId.toString()),
460 new Prop("Vendor", device.manufacturer()),
461 new Prop("H/W Version", device.hwVersion()),
462 new Prop("S/W Version", device.swVersion()),
463 new Prop("Serial Number", device.serialNumber()),
464 new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
465 new Separator(),
466 new Prop("Master", master(deviceId)),
467 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
468 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
469 new Separator(),
470 new Prop("Ports", Integer.toString(portCount)),
471 new Prop("Flows", Integer.toString(flowCount))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700472 }
473
474 protected int getFlowCount(DeviceId deviceId) {
475 int count = 0;
476 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
477 while (it.hasNext()) {
478 count++;
479 it.next();
480 }
481 return count;
482 }
483
484 // Counts all entries that egress on the given device links.
485 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
486 List<FlowEntry> entries = new ArrayList<>();
487 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
488 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
489 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
490 while (it.hasNext()) {
491 entries.add(it.next());
492 }
493
494 // Add all edge links to the set
495 if (hosts != null) {
496 for (Host host : hosts) {
497 links.add(new DefaultEdgeLink(host.providerId(),
498 new ConnectPoint(host.id(), P0),
499 host.location(), false));
500 }
501 }
502
503 Map<Link, Integer> counts = new HashMap<>();
504 for (Link link : links) {
505 counts.put(link, getEgressFlows(link, entries));
506 }
507 return counts;
508 }
509
510 // Counts all entries that egress on the link source port.
511 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
512 int count = 0;
513 PortNumber out = link.src().port();
514 for (FlowEntry entry : entries) {
515 TrafficTreatment treatment = entry.treatment();
Ray Milkey42507352015-03-20 15:16:10 -0700516 for (Instruction instruction : treatment.allInstructions()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700517 if (instruction.type() == Instruction.Type.OUTPUT &&
518 ((OutputInstruction) instruction).port().equals(out)) {
519 count++;
520 }
521 }
522 }
523 return count;
524 }
525
526
527 // Returns host details response.
528 protected ObjectNode hostDetails(HostId hostId, long sid) {
529 Host host = hostService.getHost(hostId);
530 Annotations annot = host.annotations();
531 String type = annot.value(AnnotationKeys.TYPE);
532 String name = annot.value(AnnotationKeys.NAME);
533 String vlan = host.vlan().toString();
Simon Huntd2747a02015-04-30 22:41:16 -0700534 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700535 json(isNullOrEmpty(name) ? hostId.toString() : name,
536 isNullOrEmpty(type) ? "endstation" : type,
537 new Prop("MAC", host.mac().toString()),
538 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
539 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
540 new Separator(),
541 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
542 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700543 }
544
545
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700546 // Produces JSON message to trigger flow traffic overview visualization
547 protected ObjectNode trafficSummaryMessage(StatsType type) {
Simon Huntda580882015-05-12 20:58:18 -0700548 ObjectNode payload = objectNode();
549 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700550 payload.set("paths", paths);
551
Simon Huntda580882015-05-12 20:58:18 -0700552 ObjectNode pathNodeN = objectNode();
553 ArrayNode linksNodeN = arrayNode();
554 ArrayNode labelsN = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700555
556 pathNodeN.put("class", "plain").put("traffic", false);
557 pathNodeN.set("links", linksNodeN);
558 pathNodeN.set("labels", labelsN);
559 paths.add(pathNodeN);
560
Simon Huntda580882015-05-12 20:58:18 -0700561 ObjectNode pathNodeT = objectNode();
562 ArrayNode linksNodeT = arrayNode();
563 ArrayNode labelsT = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700564
565 pathNodeT.put("class", "secondary").put("traffic", true);
566 pathNodeT.set("links", linksNodeT);
567 pathNodeT.set("labels", labelsT);
568 paths.add(pathNodeT);
569
570 for (BiLink link : consolidateLinks(linkService.getLinks())) {
571 boolean bi = link.two != null;
572 if (isInfrastructureEgress(link.one) ||
573 (bi && isInfrastructureEgress(link.two))) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700574 if (type == FLOW) {
575 link.addLoad(flowStatsService.load(link.one));
576 link.addLoad(bi ? flowStatsService.load(link.two) : null);
577 } else if (type == PORT) {
578 link.addLoad(portStatsService.load(link.one.src()), BPS_THRESHOLD);
579 link.addLoad(bi ? portStatsService.load(link.two.src()) : null, BPS_THRESHOLD);
580 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700581 if (link.hasTraffic) {
582 linksNodeT.add(compactLinkString(link.one));
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700583 labelsT.add(type == PORT ?
584 formatBytes(link.rate) + "ps" :
585 formatBytes(link.bytes));
Thomas Vachuska329af532015-03-10 02:08:33 -0700586 } else {
587 linksNodeN.add(compactLinkString(link.one));
588 labelsN.add("");
589 }
590 }
591 }
Simon Huntd2747a02015-04-30 22:41:16 -0700592 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700593 }
594
595 private Collection<BiLink> consolidateLinks(Iterable<Link> links) {
596 Map<LinkKey, BiLink> biLinks = new HashMap<>();
597 for (Link link : links) {
598 addLink(biLinks, link);
599 }
600 return biLinks.values();
601 }
602
603 // Produces JSON message to trigger flow overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700604 protected ObjectNode flowSummaryMessage(Set<Device> devices) {
Simon Huntda580882015-05-12 20:58:18 -0700605 ObjectNode payload = objectNode();
606 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700607 payload.set("paths", paths);
608
609 for (Device device : devices) {
610 Map<Link, Integer> counts = getFlowCounts(device.id());
611 for (Link link : counts.keySet()) {
612 addLinkFlows(link, paths, counts.get(link));
613 }
614 }
Simon Huntd2747a02015-04-30 22:41:16 -0700615 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700616 }
617
618 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
Simon Huntda580882015-05-12 20:58:18 -0700619 ObjectNode pathNode = objectNode();
620 ArrayNode linksNode = arrayNode();
621 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700622 boolean noFlows = count == null || count == 0;
623 pathNode.put("class", noFlows ? "secondary" : "primary");
624 pathNode.put("traffic", false);
625 pathNode.set("links", linksNode.add(compactLinkString(link)));
626 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
627 (count == 1 ? " flow" : " flows"))));
628 paths.add(pathNode);
629 }
630
631
632 // Produces JSON message to trigger traffic visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700633 protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
Simon Huntda580882015-05-12 20:58:18 -0700634 ObjectNode payload = objectNode();
635 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700636 payload.set("paths", paths);
637
638 // Classify links based on their traffic traffic first...
639 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
640
641 // Then separate the links into their respective classes and send them out.
642 Map<String, ObjectNode> pathNodes = new HashMap<>();
643 for (BiLink biLink : biLinks.values()) {
644 boolean hasTraffic = biLink.hasTraffic;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700645 String tc = (biLink.classes() + (hasTraffic ? " animated" : "")).trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700646 ObjectNode pathNode = pathNodes.get(tc);
647 if (pathNode == null) {
Simon Huntda580882015-05-12 20:58:18 -0700648 pathNode = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700649 .put("class", tc).put("traffic", hasTraffic);
Simon Huntda580882015-05-12 20:58:18 -0700650 pathNode.set("links", arrayNode());
651 pathNode.set("labels", arrayNode());
Thomas Vachuska329af532015-03-10 02:08:33 -0700652 pathNodes.put(tc, pathNode);
653 paths.add(pathNode);
654 }
655 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
656 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
657 }
658
Simon Huntd2747a02015-04-30 22:41:16 -0700659 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700660 }
661
662 // Classifies the link traffic according to the specified classes.
663 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
664 Map<LinkKey, BiLink> biLinks = new HashMap<>();
665 for (TrafficClass trafficClass : trafficClasses) {
666 for (Intent intent : trafficClass.intents) {
667 boolean isOptical = intent instanceof OpticalConnectivityIntent;
668 List<Intent> installables = intentService.getInstallableIntents(intent.key());
669 if (installables != null) {
670 for (Intent installable : installables) {
671 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
672 if (installable instanceof PathIntent) {
673 classifyLinks(type, biLinks, trafficClass.showTraffic,
674 ((PathIntent) installable).path().links());
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700675 } else if (installable instanceof FlowRuleIntent) {
676 classifyLinks(type, biLinks, trafficClass.showTraffic,
677 linkResources(installable));
Thomas Vachuska329af532015-03-10 02:08:33 -0700678 } else if (installable instanceof LinkCollectionIntent) {
679 classifyLinks(type, biLinks, trafficClass.showTraffic,
680 ((LinkCollectionIntent) installable).links());
681 } else if (installable instanceof OpticalPathIntent) {
682 classifyLinks(type, biLinks, trafficClass.showTraffic,
683 ((OpticalPathIntent) installable).path().links());
684 }
685 }
686 }
687 }
688 }
689 return biLinks;
690 }
691
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700692 // Extracts links from the specified flow rule intent resources
693 private Collection<Link> linkResources(Intent installable) {
694 ImmutableList.Builder<Link> builder = ImmutableList.builder();
695 for (NetworkResource r : installable.resources()) {
696 if (r instanceof Link) {
697 builder.add((Link) r);
698 }
699 }
700 return builder.build();
701 }
702
Thomas Vachuska329af532015-03-10 02:08:33 -0700703
704 // Adds the link segments (path or tree) associated with the specified
705 // connectivity intent
706 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
707 boolean showTraffic, Iterable<Link> links) {
708 if (links != null) {
709 for (Link link : links) {
710 BiLink biLink = addLink(biLinks, link);
711 if (isInfrastructureEgress(link)) {
712 if (showTraffic) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700713 biLink.addLoad(flowStatsService.load(link));
Thomas Vachuska329af532015-03-10 02:08:33 -0700714 }
715 biLink.addClass(type);
716 }
717 }
718 }
719 }
720
721
Thomas Vachuska583bc632015-04-14 10:10:57 -0700722 static BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700723 LinkKey key = canonicalLinkKey(link);
724 BiLink biLink = biLinks.get(key);
725 if (biLink != null) {
726 biLink.setOther(link);
727 } else {
728 biLink = new BiLink(key, link);
729 biLinks.put(key, biLink);
730 }
731 return biLink;
732 }
733
734
735 // Adds the link segments (path or tree) associated with the specified
736 // connectivity intent
737 protected void addPathTraffic(ArrayNode paths, String type, String trafficType,
738 Iterable<Link> links) {
Simon Huntda580882015-05-12 20:58:18 -0700739 ObjectNode pathNode = objectNode();
740 ArrayNode linksNode = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700741
742 if (links != null) {
Simon Huntda580882015-05-12 20:58:18 -0700743 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700744 boolean hasTraffic = false;
745 for (Link link : links) {
746 if (isInfrastructureEgress(link)) {
747 linksNode.add(compactLinkString(link));
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700748 Load load = flowStatsService.load(link);
Thomas Vachuska329af532015-03-10 02:08:33 -0700749 String label = "";
750 if (load.rate() > 0) {
751 hasTraffic = true;
752 label = formatBytes(load.latest());
753 }
754 labels.add(label);
755 }
756 }
757 pathNode.put("class", hasTraffic ? type + " " + trafficType : type);
758 pathNode.put("traffic", hasTraffic);
759 pathNode.set("links", linksNode);
760 pathNode.set("labels", labels);
761 paths.add(pathNode);
762 }
763 }
764
765 // Poor-mans formatting to get the labels with byte counts looking nice.
766 private String formatBytes(long bytes) {
Simon Hunt96641372015-06-02 15:53:25 -0700767 // TODO: multiply everything by 8 to compute bits/second
Thomas Vachuska329af532015-03-10 02:08:33 -0700768 String unit;
769 double value;
770 if (bytes > GB) {
771 value = bytes / GB;
772 unit = GB_UNIT;
773 } else if (bytes > MB) {
774 value = bytes / MB;
775 unit = MB_UNIT;
776 } else if (bytes > KB) {
777 value = bytes / KB;
778 unit = KB_UNIT;
779 } else {
780 value = bytes;
781 unit = B_UNIT;
782 }
783 DecimalFormat format = new DecimalFormat("#,###.##");
784 return format.format(value) + " " + unit;
785 }
786
787 // Formats the given number into a string.
788 private String format(Number number) {
789 DecimalFormat format = new DecimalFormat("#,###");
790 return format.format(number);
791 }
792
793 private boolean isInfrastructureEgress(Link link) {
794 return link.src().elementId() instanceof DeviceId;
795 }
796
797 // Produces compact string representation of a link.
798 private static String compactLinkString(Link link) {
799 return String.format(COMPACT, link.src().elementId(), link.src().port(),
800 link.dst().elementId(), link.dst().port());
801 }
802
803 // Produces JSON property details.
804 private ObjectNode json(String id, String type, Prop... props) {
805 ObjectMapper mapper = new ObjectMapper();
Simon Huntda580882015-05-12 20:58:18 -0700806 ObjectNode result = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700807 .put("id", id).put("type", type);
Simon Huntda580882015-05-12 20:58:18 -0700808 ObjectNode pnode = objectNode();
809 ArrayNode porder = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700810 for (Prop p : props) {
811 porder.add(p.key);
812 pnode.put(p.key, p.value);
813 }
814 result.set("propOrder", porder);
815 result.set("props", pnode);
816 return result;
817 }
818
819 // Produces canonical link key, i.e. one that will match link and its inverse.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700820 static LinkKey canonicalLinkKey(Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700821 String sn = link.src().elementId().toString();
822 String dn = link.dst().elementId().toString();
823 return sn.compareTo(dn) < 0 ?
824 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
825 }
826
827 // Representation of link and its inverse and any traffic data.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700828 static class BiLink {
Thomas Vachuska329af532015-03-10 02:08:33 -0700829 public final LinkKey key;
830 public final Link one;
831 public Link two;
832 public boolean hasTraffic = false;
833 public long bytes = 0;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700834
835 private Set<String> classes = new HashSet<>();
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700836 private long rate;
Thomas Vachuska329af532015-03-10 02:08:33 -0700837
838 BiLink(LinkKey key, Link link) {
839 this.key = key;
840 this.one = link;
841 }
842
843 void setOther(Link link) {
844 this.two = link;
845 }
846
847 void addLoad(Load load) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700848 addLoad(load, 0);
849 }
850
851 void addLoad(Load load, long threshold) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700852 if (load != null) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700853 this.hasTraffic = hasTraffic || load.rate() > threshold;
Thomas Vachuska329af532015-03-10 02:08:33 -0700854 this.bytes += load.latest();
Thomas Vachuskafdbc4c22015-05-29 15:53:01 -0700855 this.rate += load.rate();
Thomas Vachuska329af532015-03-10 02:08:33 -0700856 }
857 }
858
859 void addClass(String trafficClass) {
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700860 classes.add(trafficClass);
861 }
862
863 String classes() {
864 StringBuilder sb = new StringBuilder();
865 classes.forEach(c -> sb.append(c).append(" "));
866 return sb.toString().trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700867 }
868 }
869
870 // Auxiliary key/value carrier.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700871 static class Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700872 public final String key;
873 public final String value;
874
875 protected Prop(String key, String value) {
876 this.key = key;
877 this.value = value;
878 }
879 }
880
881 // Auxiliary properties separator
Thomas Vachuska583bc632015-04-14 10:10:57 -0700882 static class Separator extends Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700883 protected Separator() {
884 super("-", "");
885 }
886 }
887
888 // Auxiliary carrier of data for requesting traffic message.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700889 static class TrafficClass {
Thomas Vachuska329af532015-03-10 02:08:33 -0700890 public final boolean showTraffic;
891 public final String type;
892 public final Iterable<Intent> intents;
893
894 TrafficClass(String type, Iterable<Intent> intents) {
895 this(type, intents, false);
896 }
897
898 TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
899 this.type = type;
900 this.intents = intents;
901 this.showTraffic = showTraffic;
902 }
903 }
904
905}