blob: 3ab716124d49b926eae50746c6ea5aea27850669 [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;
Thomas Vachuska329af532015-03-10 02:08:33 -070019import com.fasterxml.jackson.databind.node.ArrayNode;
20import com.fasterxml.jackson.databind.node.ObjectNode;
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -070021import com.google.common.collect.ImmutableList;
Thomas Vachuska329af532015-03-10 02:08:33 -070022import org.onlab.osgi.ServiceDirectory;
23import org.onlab.packet.IpAddress;
24import org.onosproject.cluster.ClusterEvent;
25import org.onosproject.cluster.ClusterService;
26import org.onosproject.cluster.ControllerNode;
27import org.onosproject.cluster.NodeId;
28import org.onosproject.core.CoreService;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -070029import org.onosproject.incubator.net.PortStatisticsService;
Thomas Vachuska329af532015-03-10 02:08:33 -070030import 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;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -070092import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
Thomas Vachuska329af532015-03-10 02:08:33 -070093import 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 Vachuska204cb6c2015-06-04 00:03:06 -0700104import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.FLOW;
105import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.PORT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700106
107/**
108 * Facility for creating messages bound for the topology viewer.
109 */
Simon Huntd7f7bcc2015-05-08 14:13:17 -0700110@Deprecated
Simon Hunta0ddb022015-05-01 09:53:01 -0700111public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
Thomas Vachuska329af532015-03-10 02:08:33 -0700112
Simon Huntd2747a02015-04-30 22:41:16 -0700113 protected static final Logger log =
114 LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700115
Simon Huntd2747a02015-04-30 22:41:16 -0700116 private static final ProviderId PID =
117 new ProviderId("core", "org.onosproject.core", true);
Thomas Vachuska329af532015-03-10 02:08:33 -0700118 private static final String COMPACT = "%s/%s-%s/%s";
119
120 private static final double KB = 1024;
121 private static final double MB = 1024 * KB;
122 private static final double GB = 1024 * MB;
123
Simon Hunt96641372015-06-02 15:53:25 -0700124 // TODO: change GB to Gb (when we compute bits/second)
Thomas Vachuska329af532015-03-10 02:08:33 -0700125 private static final String GB_UNIT = "GB";
126 private static final String MB_UNIT = "MB";
127 private static final String KB_UNIT = "KB";
128 private static final String B_UNIT = "B";
129
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700130 private static final double BPS_THRESHOLD = 4 * KB;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700131
Thomas Vachuska329af532015-03-10 02:08:33 -0700132 protected ServiceDirectory directory;
133 protected ClusterService clusterService;
134 protected DeviceService deviceService;
135 protected LinkService linkService;
136 protected HostService hostService;
137 protected MastershipService mastershipService;
138 protected IntentService intentService;
139 protected FlowRuleService flowService;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700140 protected StatisticService flowStatsService;
141 protected PortStatisticsService portStatsService;
Thomas Vachuska329af532015-03-10 02:08:33 -0700142 protected TopologyService topologyService;
143
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700144 protected enum StatsType {
145 FLOW, PORT
146 }
147
Thomas Vachuska329af532015-03-10 02:08:33 -0700148 private String version;
149
150 // TODO: extract into an external & durable state; good enough for now and demo
151 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
152
153 /**
Thomas Vachuska329af532015-03-10 02:08:33 -0700154 * Returns read-only view of the meta-ui information.
155 *
156 * @return map of id to meta-ui mementos
157 */
158 static Map<String, ObjectNode> getMetaUi() {
159 return Collections.unmodifiableMap(metaUi);
160 }
161
162 @Override
163 public void init(UiConnection connection, ServiceDirectory directory) {
164 super.init(connection, directory);
165 this.directory = checkNotNull(directory, "Directory cannot be null");
166 clusterService = directory.get(ClusterService.class);
167 deviceService = directory.get(DeviceService.class);
168 linkService = directory.get(LinkService.class);
169 hostService = directory.get(HostService.class);
170 mastershipService = directory.get(MastershipService.class);
171 intentService = directory.get(IntentService.class);
172 flowService = directory.get(FlowRuleService.class);
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700173 flowStatsService = directory.get(StatisticService.class);
174 portStatsService = directory.get(PortStatisticsService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700175 topologyService = directory.get(TopologyService.class);
176
177 String ver = directory.get(CoreService.class).version().toString();
178 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
179 }
180
Thomas Vachuska329af532015-03-10 02:08:33 -0700181 // Returns the specified set of IP addresses as a string.
182 private String ip(Set<IpAddress> ipAddresses) {
183 Iterator<IpAddress> it = ipAddresses.iterator();
184 return it.hasNext() ? it.next().toString() : "unknown";
185 }
186
187 // Produces JSON structure from annotations.
188 private JsonNode props(Annotations annotations) {
Simon Huntda580882015-05-12 20:58:18 -0700189 ObjectNode props = objectNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700190 if (annotations != null) {
191 for (String key : annotations.keys()) {
192 props.put(key, annotations.value(key));
193 }
194 }
195 return props;
196 }
197
198 // Produces an informational log message event bound to the client.
199 protected ObjectNode info(long id, String message) {
200 return message("info", id, message);
201 }
202
203 // Produces a warning log message event bound to the client.
204 protected ObjectNode warning(long id, String message) {
205 return message("warning", id, message);
206 }
207
208 // Produces an error log message event bound to the client.
209 protected ObjectNode error(long id, String message) {
210 return message("error", id, message);
211 }
212
213 // Produces a log message event bound to the client.
214 private ObjectNode message(String severity, long id, String message) {
Simon Huntda580882015-05-12 20:58:18 -0700215 ObjectNode payload = objectNode()
Simon Huntd2747a02015-04-30 22:41:16 -0700216 .put("severity", severity)
217 .put("message", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700218
Simon Huntd2747a02015-04-30 22:41:16 -0700219 return JsonUtils.envelope("message", id, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700220 }
221
222 // Produces a set of all hosts listed in the specified JSON array.
223 protected Set<Host> getHosts(ArrayNode array) {
224 Set<Host> hosts = new HashSet<>();
225 if (array != null) {
226 for (JsonNode node : array) {
227 try {
228 addHost(hosts, hostId(node.asText()));
229 } catch (IllegalArgumentException e) {
230 log.debug("Skipping ID {}", node.asText());
231 }
232 }
233 }
234 return hosts;
235 }
236
237 // Adds the specified host to the set of hosts.
238 private void addHost(Set<Host> hosts, HostId hostId) {
239 Host host = hostService.getHost(hostId);
240 if (host != null) {
241 hosts.add(host);
242 }
243 }
244
245
246 // Produces a set of all devices listed in the specified JSON array.
247 protected Set<Device> getDevices(ArrayNode array) {
248 Set<Device> devices = new HashSet<>();
249 if (array != null) {
250 for (JsonNode node : array) {
251 try {
252 addDevice(devices, deviceId(node.asText()));
253 } catch (IllegalArgumentException e) {
254 log.debug("Skipping ID {}", node.asText());
255 }
256 }
257 }
258 return devices;
259 }
260
261 private void addDevice(Set<Device> devices, DeviceId deviceId) {
262 Device device = deviceService.getDevice(deviceId);
263 if (device != null) {
264 devices.add(device);
265 }
266 }
267
268 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
269 try {
270 addHost(hosts, hostId(hover));
271 } catch (IllegalArgumentException e) {
272 try {
273 addDevice(devices, deviceId(hover));
274 } catch (IllegalArgumentException ne) {
275 log.debug("Skipping ID {}", hover);
276 }
277 }
278 }
279
280 // Produces a cluster instance message to the client.
281 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
282 ControllerNode node = event.subject();
283 int switchCount = mastershipService.getDevicesOf(node.id()).size();
Simon Huntda580882015-05-12 20:58:18 -0700284 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700285 .put("id", node.id().toString())
286 .put("ip", node.ip().toString())
287 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700288 .put("uiAttached", node.equals(clusterService.getLocalNode()))
Thomas Vachuska329af532015-03-10 02:08:33 -0700289 .put("switches", switchCount);
290
Simon Huntda580882015-05-12 20:58:18 -0700291 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700292 labels.add(node.id().toString());
293 labels.add(node.ip().toString());
294
295 // Add labels, props and stuff the payload into envelope.
296 payload.set("labels", labels);
297 addMetaUi(node.id().toString(), payload);
298
299 String type = messageType != null ? messageType :
300 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
301 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
302 "addInstance")));
Simon Huntd2747a02015-04-30 22:41:16 -0700303 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700304 }
305
306 // Produces a device event message to the client.
307 protected ObjectNode deviceMessage(DeviceEvent event) {
308 Device device = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700309 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700310 .put("id", device.id().toString())
311 .put("type", device.type().toString().toLowerCase())
312 .put("online", deviceService.isAvailable(device.id()))
313 .put("master", master(device.id()));
314
315 // Generate labels: id, chassis id, no-label, optional-name
316 String name = device.annotations().value(AnnotationKeys.NAME);
Simon Huntda580882015-05-12 20:58:18 -0700317 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700318 labels.add("");
319 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
320 labels.add(device.id().toString());
321
322 // Add labels, props and stuff the payload into envelope.
323 payload.set("labels", labels);
324 payload.set("props", props(device.annotations()));
325 addGeoLocation(device, payload);
326 addMetaUi(device.id().toString(), payload);
327
328 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
329 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
Simon Huntd2747a02015-04-30 22:41:16 -0700330 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700331 }
332
333 // Produces a link event message to the client.
334 protected ObjectNode linkMessage(LinkEvent event) {
335 Link link = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700336 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700337 .put("id", compactLinkString(link))
338 .put("type", link.type().toString().toLowerCase())
339 .put("online", link.state() == Link.State.ACTIVE)
340 .put("linkWidth", 1.2)
341 .put("src", link.src().deviceId().toString())
342 .put("srcPort", link.src().port().toString())
343 .put("dst", link.dst().deviceId().toString())
344 .put("dstPort", link.dst().port().toString());
345 String type = (event.type() == LINK_ADDED) ? "addLink" :
346 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
Simon Huntd2747a02015-04-30 22:41:16 -0700347 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700348 }
349
350 // Produces a host event message to the client.
351 protected ObjectNode hostMessage(HostEvent event) {
352 Host host = event.subject();
353 String hostType = host.annotations().value(AnnotationKeys.TYPE);
Simon Huntda580882015-05-12 20:58:18 -0700354 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700355 .put("id", host.id().toString())
356 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
357 .put("ingress", compactLinkString(edgeLink(host, true)))
358 .put("egress", compactLinkString(edgeLink(host, false)));
Simon Huntda580882015-05-12 20:58:18 -0700359 payload.set("cp", hostConnect(host.location()));
360 payload.set("labels", labels(ip(host.ipAddresses()),
Thomas Vachuska329af532015-03-10 02:08:33 -0700361 host.mac().toString()));
362 payload.set("props", props(host.annotations()));
363 addGeoLocation(host, payload);
364 addMetaUi(host.id().toString(), payload);
365
366 String type = (event.type() == HOST_ADDED) ? "addHost" :
367 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
Simon Huntd2747a02015-04-30 22:41:16 -0700368 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700369 }
370
371 // Encodes the specified host location into a JSON object.
Simon Huntda580882015-05-12 20:58:18 -0700372 private ObjectNode hostConnect(HostLocation location) {
373 return objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700374 .put("device", location.deviceId().toString())
375 .put("port", location.port().toLong());
376 }
377
378 // Encodes the specified list of labels a JSON array.
Simon Huntda580882015-05-12 20:58:18 -0700379 private ArrayNode labels(String... labels) {
380 ArrayNode json = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700381 for (String label : labels) {
382 json.add(label);
383 }
384 return json;
385 }
386
387 // Returns the name of the master node for the specified device id.
388 private String master(DeviceId deviceId) {
389 NodeId master = mastershipService.getMasterFor(deviceId);
390 return master != null ? master.toString() : "";
391 }
392
393 // Generates an edge link from the specified host location.
394 private EdgeLink edgeLink(Host host, boolean ingress) {
395 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
396 host.location(), ingress);
397 }
398
399 // Adds meta UI information for the specified object.
400 private void addMetaUi(String id, ObjectNode payload) {
401 ObjectNode meta = metaUi.get(id);
402 if (meta != null) {
403 payload.set("metaUi", meta);
404 }
405 }
406
407 // Adds a geo location JSON to the specified payload object.
408 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
409 Annotations annotations = annotated.annotations();
410 if (annotations == null) {
411 return;
412 }
413
414 String slat = annotations.value(AnnotationKeys.LATITUDE);
415 String slng = annotations.value(AnnotationKeys.LONGITUDE);
416 try {
417 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
418 double lat = Double.parseDouble(slat);
419 double lng = Double.parseDouble(slng);
Simon Huntda580882015-05-12 20:58:18 -0700420 ObjectNode loc = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700421 .put("type", "latlng").put("lat", lat).put("lng", lng);
422 payload.set("location", loc);
423 }
424 } catch (NumberFormatException e) {
425 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
426 }
427 }
428
429 // Updates meta UI information for the specified object.
Simon Huntd2747a02015-04-30 22:41:16 -0700430 protected void updateMetaUi(ObjectNode payload) {
431 metaUi.put(JsonUtils.string(payload, "id"),
432 JsonUtils.node(payload, "memento"));
Thomas Vachuska329af532015-03-10 02:08:33 -0700433 }
434
435 // Returns summary response.
436 protected ObjectNode summmaryMessage(long sid) {
437 Topology topology = topologyService.currentTopology();
Simon Huntd2747a02015-04-30 22:41:16 -0700438 return JsonUtils.envelope("showSummary", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700439 json("ONOS Summary", "node",
440 new Prop("Devices", format(topology.deviceCount())),
441 new Prop("Links", format(topology.linkCount())),
442 new Prop("Hosts", format(hostService.getHostCount())),
443 new Prop("Topology SCCs", format(topology.clusterCount())),
444 new Separator(),
445 new Prop("Intents", format(intentService.getIntentCount())),
446 new Prop("Flows", format(flowService.getFlowRuleCount())),
447 new Prop("Version", version)));
Thomas Vachuska329af532015-03-10 02:08:33 -0700448 }
449
450 // Returns device details response.
451 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
452 Device device = deviceService.getDevice(deviceId);
453 Annotations annot = device.annotations();
454 String name = annot.value(AnnotationKeys.NAME);
455 int portCount = deviceService.getPorts(deviceId).size();
456 int flowCount = getFlowCount(deviceId);
Simon Huntd2747a02015-04-30 22:41:16 -0700457 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700458 json(isNullOrEmpty(name) ? deviceId.toString() : name,
459 device.type().toString().toLowerCase(),
460 new Prop("URI", deviceId.toString()),
461 new Prop("Vendor", device.manufacturer()),
462 new Prop("H/W Version", device.hwVersion()),
463 new Prop("S/W Version", device.swVersion()),
464 new Prop("Serial Number", device.serialNumber()),
465 new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
466 new Separator(),
467 new Prop("Master", master(deviceId)),
468 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
469 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
470 new Separator(),
471 new Prop("Ports", Integer.toString(portCount)),
472 new Prop("Flows", Integer.toString(flowCount))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700473 }
474
475 protected int getFlowCount(DeviceId deviceId) {
476 int count = 0;
477 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
478 while (it.hasNext()) {
479 count++;
480 it.next();
481 }
482 return count;
483 }
484
485 // Counts all entries that egress on the given device links.
486 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
487 List<FlowEntry> entries = new ArrayList<>();
488 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
489 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
490 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
491 while (it.hasNext()) {
492 entries.add(it.next());
493 }
494
495 // Add all edge links to the set
496 if (hosts != null) {
497 for (Host host : hosts) {
498 links.add(new DefaultEdgeLink(host.providerId(),
499 new ConnectPoint(host.id(), P0),
500 host.location(), false));
501 }
502 }
503
504 Map<Link, Integer> counts = new HashMap<>();
505 for (Link link : links) {
506 counts.put(link, getEgressFlows(link, entries));
507 }
508 return counts;
509 }
510
511 // Counts all entries that egress on the link source port.
512 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
513 int count = 0;
514 PortNumber out = link.src().port();
515 for (FlowEntry entry : entries) {
516 TrafficTreatment treatment = entry.treatment();
Ray Milkey42507352015-03-20 15:16:10 -0700517 for (Instruction instruction : treatment.allInstructions()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700518 if (instruction.type() == Instruction.Type.OUTPUT &&
519 ((OutputInstruction) instruction).port().equals(out)) {
520 count++;
521 }
522 }
523 }
524 return count;
525 }
526
527
528 // Returns host details response.
529 protected ObjectNode hostDetails(HostId hostId, long sid) {
530 Host host = hostService.getHost(hostId);
531 Annotations annot = host.annotations();
532 String type = annot.value(AnnotationKeys.TYPE);
533 String name = annot.value(AnnotationKeys.NAME);
534 String vlan = host.vlan().toString();
Simon Huntd2747a02015-04-30 22:41:16 -0700535 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700536 json(isNullOrEmpty(name) ? hostId.toString() : name,
537 isNullOrEmpty(type) ? "endstation" : type,
538 new Prop("MAC", host.mac().toString()),
539 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
540 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
541 new Separator(),
542 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
543 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700544 }
545
546
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700547 // Produces JSON message to trigger flow traffic overview visualization
548 protected ObjectNode trafficSummaryMessage(StatsType type) {
Simon Huntda580882015-05-12 20:58:18 -0700549 ObjectNode payload = objectNode();
550 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700551 payload.set("paths", paths);
552
Simon Huntda580882015-05-12 20:58:18 -0700553 ObjectNode pathNodeN = objectNode();
554 ArrayNode linksNodeN = arrayNode();
555 ArrayNode labelsN = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700556
557 pathNodeN.put("class", "plain").put("traffic", false);
558 pathNodeN.set("links", linksNodeN);
559 pathNodeN.set("labels", labelsN);
560 paths.add(pathNodeN);
561
Simon Huntda580882015-05-12 20:58:18 -0700562 ObjectNode pathNodeT = objectNode();
563 ArrayNode linksNodeT = arrayNode();
564 ArrayNode labelsT = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700565
566 pathNodeT.put("class", "secondary").put("traffic", true);
567 pathNodeT.set("links", linksNodeT);
568 pathNodeT.set("labels", labelsT);
569 paths.add(pathNodeT);
570
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700571 Map<LinkKey, BiLink> biLinks = consolidateLinks(linkService.getLinks());
572 addEdgeLinks(biLinks);
573 for (BiLink link : biLinks.values()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700574 boolean bi = link.two != null;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700575 if (type == FLOW) {
576 link.addLoad(getLinkLoad(link.one));
577 link.addLoad(bi ? getLinkLoad(link.two) : null);
578 } else if (type == PORT) {
579 link.addLoad(portStatsService.load(link.one.src()), BPS_THRESHOLD);
580 link.addLoad(portStatsService.load(link.one.dst()), BPS_THRESHOLD);
581 }
582 if (link.hasTraffic) {
583 linksNodeT.add(compactLinkString(link.one));
584 labelsT.add(type == PORT ?
585 formatBytes(link.rate) + "ps" :
586 formatBytes(link.bytes));
587 } else {
588 linksNodeN.add(compactLinkString(link.one));
589 labelsN.add("");
Thomas Vachuska329af532015-03-10 02:08:33 -0700590 }
591 }
Simon Huntd2747a02015-04-30 22:41:16 -0700592 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700593 }
594
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700595 private Load getLinkLoad(Link link) {
596 if (link.src().elementId() instanceof DeviceId) {
597 return flowStatsService.load(link);
598 }
599 return null;
600 }
601
602 private void addEdgeLinks(Map<LinkKey, BiLink> biLinks) {
603 hostService.getHosts().forEach(host -> {
604 addLink(biLinks, createEdgeLink(host.location(), false));
605 });
606 }
607
608 private Map<LinkKey, BiLink> consolidateLinks(Iterable<Link> links) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700609 Map<LinkKey, BiLink> biLinks = new HashMap<>();
610 for (Link link : links) {
611 addLink(biLinks, link);
612 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700613 return biLinks;
Thomas Vachuska329af532015-03-10 02:08:33 -0700614 }
615
616 // Produces JSON message to trigger flow overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700617 protected ObjectNode flowSummaryMessage(Set<Device> devices) {
Simon Huntda580882015-05-12 20:58:18 -0700618 ObjectNode payload = objectNode();
619 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700620 payload.set("paths", paths);
621
622 for (Device device : devices) {
623 Map<Link, Integer> counts = getFlowCounts(device.id());
624 for (Link link : counts.keySet()) {
625 addLinkFlows(link, paths, counts.get(link));
626 }
627 }
Simon Huntd2747a02015-04-30 22:41:16 -0700628 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700629 }
630
631 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
Simon Huntda580882015-05-12 20:58:18 -0700632 ObjectNode pathNode = objectNode();
633 ArrayNode linksNode = arrayNode();
634 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700635 boolean noFlows = count == null || count == 0;
636 pathNode.put("class", noFlows ? "secondary" : "primary");
637 pathNode.put("traffic", false);
638 pathNode.set("links", linksNode.add(compactLinkString(link)));
639 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
640 (count == 1 ? " flow" : " flows"))));
641 paths.add(pathNode);
642 }
643
644
645 // Produces JSON message to trigger traffic visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700646 protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
Simon Huntda580882015-05-12 20:58:18 -0700647 ObjectNode payload = objectNode();
648 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700649 payload.set("paths", paths);
650
651 // Classify links based on their traffic traffic first...
652 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
653
654 // Then separate the links into their respective classes and send them out.
655 Map<String, ObjectNode> pathNodes = new HashMap<>();
656 for (BiLink biLink : biLinks.values()) {
657 boolean hasTraffic = biLink.hasTraffic;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700658 String tc = (biLink.classes() + (hasTraffic ? " animated" : "")).trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700659 ObjectNode pathNode = pathNodes.get(tc);
660 if (pathNode == null) {
Simon Huntda580882015-05-12 20:58:18 -0700661 pathNode = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700662 .put("class", tc).put("traffic", hasTraffic);
Simon Huntda580882015-05-12 20:58:18 -0700663 pathNode.set("links", arrayNode());
664 pathNode.set("labels", arrayNode());
Thomas Vachuska329af532015-03-10 02:08:33 -0700665 pathNodes.put(tc, pathNode);
666 paths.add(pathNode);
667 }
668 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
669 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
670 }
671
Simon Huntd2747a02015-04-30 22:41:16 -0700672 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700673 }
674
675 // Classifies the link traffic according to the specified classes.
676 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
677 Map<LinkKey, BiLink> biLinks = new HashMap<>();
678 for (TrafficClass trafficClass : trafficClasses) {
679 for (Intent intent : trafficClass.intents) {
680 boolean isOptical = intent instanceof OpticalConnectivityIntent;
681 List<Intent> installables = intentService.getInstallableIntents(intent.key());
682 if (installables != null) {
683 for (Intent installable : installables) {
684 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
685 if (installable instanceof PathIntent) {
686 classifyLinks(type, biLinks, trafficClass.showTraffic,
687 ((PathIntent) installable).path().links());
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700688 } else if (installable instanceof FlowRuleIntent) {
689 classifyLinks(type, biLinks, trafficClass.showTraffic,
690 linkResources(installable));
Thomas Vachuska329af532015-03-10 02:08:33 -0700691 } else if (installable instanceof LinkCollectionIntent) {
692 classifyLinks(type, biLinks, trafficClass.showTraffic,
693 ((LinkCollectionIntent) installable).links());
694 } else if (installable instanceof OpticalPathIntent) {
695 classifyLinks(type, biLinks, trafficClass.showTraffic,
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700696 ((OpticalPathIntent) installable).path().links());
Thomas Vachuska329af532015-03-10 02:08:33 -0700697 }
698 }
699 }
700 }
701 }
702 return biLinks;
703 }
704
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700705 // Extracts links from the specified flow rule intent resources
706 private Collection<Link> linkResources(Intent installable) {
707 ImmutableList.Builder<Link> builder = ImmutableList.builder();
708 for (NetworkResource r : installable.resources()) {
709 if (r instanceof Link) {
710 builder.add((Link) r);
711 }
712 }
713 return builder.build();
714 }
715
Thomas Vachuska329af532015-03-10 02:08:33 -0700716
717 // Adds the link segments (path or tree) associated with the specified
718 // connectivity intent
719 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
720 boolean showTraffic, Iterable<Link> links) {
721 if (links != null) {
722 for (Link link : links) {
723 BiLink biLink = addLink(biLinks, link);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700724 if (showTraffic) {
725 biLink.addLoad(flowStatsService.load(link));
Thomas Vachuska329af532015-03-10 02:08:33 -0700726 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700727 biLink.addClass(type);
Thomas Vachuska329af532015-03-10 02:08:33 -0700728 }
729 }
730 }
731
732
Thomas Vachuska583bc632015-04-14 10:10:57 -0700733 static BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700734 LinkKey key = canonicalLinkKey(link);
735 BiLink biLink = biLinks.get(key);
736 if (biLink != null) {
737 biLink.setOther(link);
738 } else {
739 biLink = new BiLink(key, link);
740 biLinks.put(key, biLink);
741 }
742 return biLink;
743 }
744
Thomas Vachuska329af532015-03-10 02:08:33 -0700745 // Poor-mans formatting to get the labels with byte counts looking nice.
746 private String formatBytes(long bytes) {
Simon Hunt96641372015-06-02 15:53:25 -0700747 // TODO: multiply everything by 8 to compute bits/second
Thomas Vachuska329af532015-03-10 02:08:33 -0700748 String unit;
749 double value;
750 if (bytes > GB) {
751 value = bytes / GB;
752 unit = GB_UNIT;
753 } else if (bytes > MB) {
754 value = bytes / MB;
755 unit = MB_UNIT;
756 } else if (bytes > KB) {
757 value = bytes / KB;
758 unit = KB_UNIT;
759 } else {
760 value = bytes;
761 unit = B_UNIT;
762 }
763 DecimalFormat format = new DecimalFormat("#,###.##");
764 return format.format(value) + " " + unit;
765 }
766
767 // Formats the given number into a string.
768 private String format(Number number) {
769 DecimalFormat format = new DecimalFormat("#,###");
770 return format.format(number);
771 }
772
Thomas Vachuska329af532015-03-10 02:08:33 -0700773 // Produces compact string representation of a link.
774 private static String compactLinkString(Link link) {
775 return String.format(COMPACT, link.src().elementId(), link.src().port(),
776 link.dst().elementId(), link.dst().port());
777 }
778
779 // Produces JSON property details.
780 private ObjectNode json(String id, String type, Prop... props) {
Simon Huntda580882015-05-12 20:58:18 -0700781 ObjectNode result = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700782 .put("id", id).put("type", type);
Simon Huntda580882015-05-12 20:58:18 -0700783 ObjectNode pnode = objectNode();
784 ArrayNode porder = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700785 for (Prop p : props) {
786 porder.add(p.key);
787 pnode.put(p.key, p.value);
788 }
789 result.set("propOrder", porder);
790 result.set("props", pnode);
791 return result;
792 }
793
794 // Produces canonical link key, i.e. one that will match link and its inverse.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700795 static LinkKey canonicalLinkKey(Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700796 String sn = link.src().elementId().toString();
797 String dn = link.dst().elementId().toString();
798 return sn.compareTo(dn) < 0 ?
799 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
800 }
801
802 // Representation of link and its inverse and any traffic data.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700803 static class BiLink {
Thomas Vachuska329af532015-03-10 02:08:33 -0700804 public final LinkKey key;
805 public final Link one;
806 public Link two;
807 public boolean hasTraffic = false;
808 public long bytes = 0;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700809
810 private Set<String> classes = new HashSet<>();
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700811 private long rate;
Thomas Vachuska329af532015-03-10 02:08:33 -0700812
813 BiLink(LinkKey key, Link link) {
814 this.key = key;
815 this.one = link;
816 }
817
818 void setOther(Link link) {
819 this.two = link;
820 }
821
822 void addLoad(Load load) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700823 addLoad(load, 0);
824 }
825
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700826 void addLoad(Load load, double threshold) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700827 if (load != null) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700828 this.hasTraffic = hasTraffic || load.rate() > threshold;
Thomas Vachuska329af532015-03-10 02:08:33 -0700829 this.bytes += load.latest();
Thomas Vachuskafdbc4c22015-05-29 15:53:01 -0700830 this.rate += load.rate();
Thomas Vachuska329af532015-03-10 02:08:33 -0700831 }
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}