blob: 0c466f81a0eb38ae8ef4051e50b92874b2ca51e3 [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
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700120 private static final double KILO = 1024;
121 private static final double MEGA = 1024 * KILO;
122 private static final double GIGA = 1024 * MEGA;
Thomas Vachuska329af532015-03-10 02:08:33 -0700123
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700124 private static final String GBITS_UNIT = "Gb";
125 private static final String MBITS_UNIT = "Mb";
126 private static final String KBITS_UNIT = "Kb";
127 private static final String BITS_UNIT = "b";
128 private static final String GBYTES_UNIT = "GB";
129 private static final String MBYTES_UNIT = "MB";
130 private static final String KBYTES_UNIT = "KB";
131 private static final String BYTES_UNIT = "B";
132 //4 Kilo Bytes as threshold
133 private static final double BPS_THRESHOLD = 4 * KILO;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700134
Thomas Vachuska329af532015-03-10 02:08:33 -0700135 protected ServiceDirectory directory;
136 protected ClusterService clusterService;
137 protected DeviceService deviceService;
138 protected LinkService linkService;
139 protected HostService hostService;
140 protected MastershipService mastershipService;
141 protected IntentService intentService;
142 protected FlowRuleService flowService;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700143 protected StatisticService flowStatsService;
144 protected PortStatisticsService portStatsService;
Thomas Vachuska329af532015-03-10 02:08:33 -0700145 protected TopologyService topologyService;
146
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700147 protected enum StatsType {
148 FLOW, PORT
149 }
150
Thomas Vachuska329af532015-03-10 02:08:33 -0700151 private String version;
152
153 // TODO: extract into an external & durable state; good enough for now and demo
154 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
155
156 /**
Thomas Vachuska329af532015-03-10 02:08:33 -0700157 * Returns read-only view of the meta-ui information.
158 *
159 * @return map of id to meta-ui mementos
160 */
161 static Map<String, ObjectNode> getMetaUi() {
162 return Collections.unmodifiableMap(metaUi);
163 }
164
165 @Override
166 public void init(UiConnection connection, ServiceDirectory directory) {
167 super.init(connection, directory);
168 this.directory = checkNotNull(directory, "Directory cannot be null");
169 clusterService = directory.get(ClusterService.class);
170 deviceService = directory.get(DeviceService.class);
171 linkService = directory.get(LinkService.class);
172 hostService = directory.get(HostService.class);
173 mastershipService = directory.get(MastershipService.class);
174 intentService = directory.get(IntentService.class);
175 flowService = directory.get(FlowRuleService.class);
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700176 flowStatsService = directory.get(StatisticService.class);
177 portStatsService = directory.get(PortStatisticsService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700178 topologyService = directory.get(TopologyService.class);
179
180 String ver = directory.get(CoreService.class).version().toString();
181 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
182 }
183
Thomas Vachuska329af532015-03-10 02:08:33 -0700184 // Returns the specified set of IP addresses as a string.
185 private String ip(Set<IpAddress> ipAddresses) {
186 Iterator<IpAddress> it = ipAddresses.iterator();
187 return it.hasNext() ? it.next().toString() : "unknown";
188 }
189
190 // Produces JSON structure from annotations.
191 private JsonNode props(Annotations annotations) {
Simon Huntda580882015-05-12 20:58:18 -0700192 ObjectNode props = objectNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700193 if (annotations != null) {
194 for (String key : annotations.keys()) {
195 props.put(key, annotations.value(key));
196 }
197 }
198 return props;
199 }
200
201 // Produces an informational log message event bound to the client.
202 protected ObjectNode info(long id, String message) {
203 return message("info", id, message);
204 }
205
206 // Produces a warning log message event bound to the client.
207 protected ObjectNode warning(long id, String message) {
208 return message("warning", id, message);
209 }
210
211 // Produces an error log message event bound to the client.
212 protected ObjectNode error(long id, String message) {
213 return message("error", id, message);
214 }
215
216 // Produces a log message event bound to the client.
217 private ObjectNode message(String severity, long id, String message) {
Simon Huntda580882015-05-12 20:58:18 -0700218 ObjectNode payload = objectNode()
Simon Huntd2747a02015-04-30 22:41:16 -0700219 .put("severity", severity)
220 .put("message", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700221
Simon Huntd2747a02015-04-30 22:41:16 -0700222 return JsonUtils.envelope("message", id, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700223 }
224
225 // Produces a set of all hosts listed in the specified JSON array.
226 protected Set<Host> getHosts(ArrayNode array) {
227 Set<Host> hosts = new HashSet<>();
228 if (array != null) {
229 for (JsonNode node : array) {
230 try {
231 addHost(hosts, hostId(node.asText()));
232 } catch (IllegalArgumentException e) {
233 log.debug("Skipping ID {}", node.asText());
234 }
235 }
236 }
237 return hosts;
238 }
239
240 // Adds the specified host to the set of hosts.
241 private void addHost(Set<Host> hosts, HostId hostId) {
242 Host host = hostService.getHost(hostId);
243 if (host != null) {
244 hosts.add(host);
245 }
246 }
247
248
249 // Produces a set of all devices listed in the specified JSON array.
250 protected Set<Device> getDevices(ArrayNode array) {
251 Set<Device> devices = new HashSet<>();
252 if (array != null) {
253 for (JsonNode node : array) {
254 try {
255 addDevice(devices, deviceId(node.asText()));
256 } catch (IllegalArgumentException e) {
257 log.debug("Skipping ID {}", node.asText());
258 }
259 }
260 }
261 return devices;
262 }
263
264 private void addDevice(Set<Device> devices, DeviceId deviceId) {
265 Device device = deviceService.getDevice(deviceId);
266 if (device != null) {
267 devices.add(device);
268 }
269 }
270
271 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
272 try {
273 addHost(hosts, hostId(hover));
274 } catch (IllegalArgumentException e) {
275 try {
276 addDevice(devices, deviceId(hover));
277 } catch (IllegalArgumentException ne) {
278 log.debug("Skipping ID {}", hover);
279 }
280 }
281 }
282
283 // Produces a cluster instance message to the client.
284 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
285 ControllerNode node = event.subject();
286 int switchCount = mastershipService.getDevicesOf(node.id()).size();
Simon Huntda580882015-05-12 20:58:18 -0700287 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700288 .put("id", node.id().toString())
289 .put("ip", node.ip().toString())
290 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700291 .put("uiAttached", node.equals(clusterService.getLocalNode()))
Thomas Vachuska329af532015-03-10 02:08:33 -0700292 .put("switches", switchCount);
293
Simon Huntda580882015-05-12 20:58:18 -0700294 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700295 labels.add(node.id().toString());
296 labels.add(node.ip().toString());
297
298 // Add labels, props and stuff the payload into envelope.
299 payload.set("labels", labels);
300 addMetaUi(node.id().toString(), payload);
301
302 String type = messageType != null ? messageType :
303 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
304 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
305 "addInstance")));
Simon Huntd2747a02015-04-30 22:41:16 -0700306 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700307 }
308
309 // Produces a device event message to the client.
310 protected ObjectNode deviceMessage(DeviceEvent event) {
311 Device device = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700312 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700313 .put("id", device.id().toString())
314 .put("type", device.type().toString().toLowerCase())
315 .put("online", deviceService.isAvailable(device.id()))
316 .put("master", master(device.id()));
317
318 // Generate labels: id, chassis id, no-label, optional-name
319 String name = device.annotations().value(AnnotationKeys.NAME);
Simon Huntda580882015-05-12 20:58:18 -0700320 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700321 labels.add("");
322 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
323 labels.add(device.id().toString());
324
325 // Add labels, props and stuff the payload into envelope.
326 payload.set("labels", labels);
327 payload.set("props", props(device.annotations()));
328 addGeoLocation(device, payload);
329 addMetaUi(device.id().toString(), payload);
330
331 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
332 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
Simon Huntd2747a02015-04-30 22:41:16 -0700333 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700334 }
335
336 // Produces a link event message to the client.
337 protected ObjectNode linkMessage(LinkEvent event) {
338 Link link = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700339 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700340 .put("id", compactLinkString(link))
341 .put("type", link.type().toString().toLowerCase())
342 .put("online", link.state() == Link.State.ACTIVE)
343 .put("linkWidth", 1.2)
344 .put("src", link.src().deviceId().toString())
345 .put("srcPort", link.src().port().toString())
346 .put("dst", link.dst().deviceId().toString())
347 .put("dstPort", link.dst().port().toString());
348 String type = (event.type() == LINK_ADDED) ? "addLink" :
349 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
Simon Huntd2747a02015-04-30 22:41:16 -0700350 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700351 }
352
353 // Produces a host event message to the client.
354 protected ObjectNode hostMessage(HostEvent event) {
355 Host host = event.subject();
356 String hostType = host.annotations().value(AnnotationKeys.TYPE);
Simon Huntda580882015-05-12 20:58:18 -0700357 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700358 .put("id", host.id().toString())
359 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
360 .put("ingress", compactLinkString(edgeLink(host, true)))
361 .put("egress", compactLinkString(edgeLink(host, false)));
Simon Huntda580882015-05-12 20:58:18 -0700362 payload.set("cp", hostConnect(host.location()));
363 payload.set("labels", labels(ip(host.ipAddresses()),
Thomas Vachuska329af532015-03-10 02:08:33 -0700364 host.mac().toString()));
365 payload.set("props", props(host.annotations()));
366 addGeoLocation(host, payload);
367 addMetaUi(host.id().toString(), payload);
368
369 String type = (event.type() == HOST_ADDED) ? "addHost" :
370 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
Simon Huntd2747a02015-04-30 22:41:16 -0700371 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700372 }
373
374 // Encodes the specified host location into a JSON object.
Simon Huntda580882015-05-12 20:58:18 -0700375 private ObjectNode hostConnect(HostLocation location) {
376 return objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700377 .put("device", location.deviceId().toString())
378 .put("port", location.port().toLong());
379 }
380
381 // Encodes the specified list of labels a JSON array.
Simon Huntda580882015-05-12 20:58:18 -0700382 private ArrayNode labels(String... labels) {
383 ArrayNode json = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700384 for (String label : labels) {
385 json.add(label);
386 }
387 return json;
388 }
389
390 // Returns the name of the master node for the specified device id.
391 private String master(DeviceId deviceId) {
392 NodeId master = mastershipService.getMasterFor(deviceId);
393 return master != null ? master.toString() : "";
394 }
395
396 // Generates an edge link from the specified host location.
397 private EdgeLink edgeLink(Host host, boolean ingress) {
398 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
399 host.location(), ingress);
400 }
401
402 // Adds meta UI information for the specified object.
403 private void addMetaUi(String id, ObjectNode payload) {
404 ObjectNode meta = metaUi.get(id);
405 if (meta != null) {
406 payload.set("metaUi", meta);
407 }
408 }
409
410 // Adds a geo location JSON to the specified payload object.
411 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
412 Annotations annotations = annotated.annotations();
413 if (annotations == null) {
414 return;
415 }
416
417 String slat = annotations.value(AnnotationKeys.LATITUDE);
418 String slng = annotations.value(AnnotationKeys.LONGITUDE);
419 try {
420 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
421 double lat = Double.parseDouble(slat);
422 double lng = Double.parseDouble(slng);
Simon Huntda580882015-05-12 20:58:18 -0700423 ObjectNode loc = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700424 .put("type", "latlng").put("lat", lat).put("lng", lng);
425 payload.set("location", loc);
426 }
427 } catch (NumberFormatException e) {
428 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
429 }
430 }
431
432 // Updates meta UI information for the specified object.
Simon Huntd2747a02015-04-30 22:41:16 -0700433 protected void updateMetaUi(ObjectNode payload) {
434 metaUi.put(JsonUtils.string(payload, "id"),
435 JsonUtils.node(payload, "memento"));
Thomas Vachuska329af532015-03-10 02:08:33 -0700436 }
437
438 // Returns summary response.
439 protected ObjectNode summmaryMessage(long sid) {
440 Topology topology = topologyService.currentTopology();
Simon Huntd2747a02015-04-30 22:41:16 -0700441 return JsonUtils.envelope("showSummary", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700442 json("ONOS Summary", "node",
443 new Prop("Devices", format(topology.deviceCount())),
444 new Prop("Links", format(topology.linkCount())),
445 new Prop("Hosts", format(hostService.getHostCount())),
446 new Prop("Topology SCCs", format(topology.clusterCount())),
447 new Separator(),
448 new Prop("Intents", format(intentService.getIntentCount())),
449 new Prop("Flows", format(flowService.getFlowRuleCount())),
450 new Prop("Version", version)));
Thomas Vachuska329af532015-03-10 02:08:33 -0700451 }
452
453 // Returns device details response.
454 protected ObjectNode deviceDetails(DeviceId deviceId, long sid) {
455 Device device = deviceService.getDevice(deviceId);
456 Annotations annot = device.annotations();
457 String name = annot.value(AnnotationKeys.NAME);
458 int portCount = deviceService.getPorts(deviceId).size();
459 int flowCount = getFlowCount(deviceId);
Simon Huntd2747a02015-04-30 22:41:16 -0700460 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700461 json(isNullOrEmpty(name) ? deviceId.toString() : name,
462 device.type().toString().toLowerCase(),
463 new Prop("URI", deviceId.toString()),
464 new Prop("Vendor", device.manufacturer()),
465 new Prop("H/W Version", device.hwVersion()),
466 new Prop("S/W Version", device.swVersion()),
467 new Prop("Serial Number", device.serialNumber()),
468 new Prop("Protocol", annot.value(AnnotationKeys.PROTOCOL)),
469 new Separator(),
470 new Prop("Master", master(deviceId)),
471 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
472 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE)),
473 new Separator(),
474 new Prop("Ports", Integer.toString(portCount)),
475 new Prop("Flows", Integer.toString(flowCount))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700476 }
477
478 protected int getFlowCount(DeviceId deviceId) {
479 int count = 0;
480 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
481 while (it.hasNext()) {
482 count++;
483 it.next();
484 }
485 return count;
486 }
487
488 // Counts all entries that egress on the given device links.
489 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
490 List<FlowEntry> entries = new ArrayList<>();
491 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
492 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
493 Iterator<FlowEntry> it = flowService.getFlowEntries(deviceId).iterator();
494 while (it.hasNext()) {
495 entries.add(it.next());
496 }
497
498 // Add all edge links to the set
499 if (hosts != null) {
500 for (Host host : hosts) {
501 links.add(new DefaultEdgeLink(host.providerId(),
502 new ConnectPoint(host.id(), P0),
503 host.location(), false));
504 }
505 }
506
507 Map<Link, Integer> counts = new HashMap<>();
508 for (Link link : links) {
509 counts.put(link, getEgressFlows(link, entries));
510 }
511 return counts;
512 }
513
514 // Counts all entries that egress on the link source port.
515 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
516 int count = 0;
517 PortNumber out = link.src().port();
518 for (FlowEntry entry : entries) {
519 TrafficTreatment treatment = entry.treatment();
Ray Milkey42507352015-03-20 15:16:10 -0700520 for (Instruction instruction : treatment.allInstructions()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700521 if (instruction.type() == Instruction.Type.OUTPUT &&
522 ((OutputInstruction) instruction).port().equals(out)) {
523 count++;
524 }
525 }
526 }
527 return count;
528 }
529
530
531 // Returns host details response.
532 protected ObjectNode hostDetails(HostId hostId, long sid) {
533 Host host = hostService.getHost(hostId);
534 Annotations annot = host.annotations();
535 String type = annot.value(AnnotationKeys.TYPE);
536 String name = annot.value(AnnotationKeys.NAME);
537 String vlan = host.vlan().toString();
Simon Huntd2747a02015-04-30 22:41:16 -0700538 return JsonUtils.envelope("showDetails", sid,
Simon Hunta0ddb022015-05-01 09:53:01 -0700539 json(isNullOrEmpty(name) ? hostId.toString() : name,
540 isNullOrEmpty(type) ? "endstation" : type,
541 new Prop("MAC", host.mac().toString()),
542 new Prop("IP", host.ipAddresses().toString().replaceAll("[\\[\\]]", "")),
543 new Prop("VLAN", vlan.equals("-1") ? "none" : vlan),
544 new Separator(),
545 new Prop("Latitude", annot.value(AnnotationKeys.LATITUDE)),
546 new Prop("Longitude", annot.value(AnnotationKeys.LONGITUDE))));
Thomas Vachuska329af532015-03-10 02:08:33 -0700547 }
548
549
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700550 // Produces JSON message to trigger flow traffic overview visualization
551 protected ObjectNode trafficSummaryMessage(StatsType type) {
Simon Huntda580882015-05-12 20:58:18 -0700552 ObjectNode payload = objectNode();
553 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700554 payload.set("paths", paths);
555
Simon Huntda580882015-05-12 20:58:18 -0700556 ObjectNode pathNodeN = objectNode();
557 ArrayNode linksNodeN = arrayNode();
558 ArrayNode labelsN = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700559
560 pathNodeN.put("class", "plain").put("traffic", false);
561 pathNodeN.set("links", linksNodeN);
562 pathNodeN.set("labels", labelsN);
563 paths.add(pathNodeN);
564
Simon Huntda580882015-05-12 20:58:18 -0700565 ObjectNode pathNodeT = objectNode();
566 ArrayNode linksNodeT = arrayNode();
567 ArrayNode labelsT = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700568
569 pathNodeT.put("class", "secondary").put("traffic", true);
570 pathNodeT.set("links", linksNodeT);
571 pathNodeT.set("labels", labelsT);
572 paths.add(pathNodeT);
573
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700574 Map<LinkKey, BiLink> biLinks = consolidateLinks(linkService.getLinks());
575 addEdgeLinks(biLinks);
576 for (BiLink link : biLinks.values()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700577 boolean bi = link.two != null;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700578 if (type == FLOW) {
579 link.addLoad(getLinkLoad(link.one));
580 link.addLoad(bi ? getLinkLoad(link.two) : null);
581 } else if (type == PORT) {
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700582 //For a bi-directional traffic links, use
583 //the max link rate of either direction
584 link.addLoad(portStatsService.load(link.one.src()),
585 BPS_THRESHOLD,
586 portStatsService.load(link.one.dst()),
587 BPS_THRESHOLD);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700588 }
589 if (link.hasTraffic) {
590 linksNodeT.add(compactLinkString(link.one));
591 labelsT.add(type == PORT ?
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700592 formatBitRate(link.rate) + "ps" :
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700593 formatBytes(link.bytes));
594 } else {
595 linksNodeN.add(compactLinkString(link.one));
596 labelsN.add("");
Thomas Vachuska329af532015-03-10 02:08:33 -0700597 }
598 }
Simon Huntd2747a02015-04-30 22:41:16 -0700599 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700600 }
601
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700602 private Load getLinkLoad(Link link) {
603 if (link.src().elementId() instanceof DeviceId) {
604 return flowStatsService.load(link);
605 }
606 return null;
607 }
608
609 private void addEdgeLinks(Map<LinkKey, BiLink> biLinks) {
610 hostService.getHosts().forEach(host -> {
Thomas Vachuska25455e72015-06-04 11:31:26 -0700611 addLink(biLinks, createEdgeLink(host, true));
612 addLink(biLinks, createEdgeLink(host, false));
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700613 });
614 }
615
616 private Map<LinkKey, BiLink> consolidateLinks(Iterable<Link> links) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700617 Map<LinkKey, BiLink> biLinks = new HashMap<>();
618 for (Link link : links) {
619 addLink(biLinks, link);
620 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700621 return biLinks;
Thomas Vachuska329af532015-03-10 02:08:33 -0700622 }
623
624 // Produces JSON message to trigger flow overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700625 protected ObjectNode flowSummaryMessage(Set<Device> devices) {
Simon Huntda580882015-05-12 20:58:18 -0700626 ObjectNode payload = objectNode();
627 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700628 payload.set("paths", paths);
629
630 for (Device device : devices) {
631 Map<Link, Integer> counts = getFlowCounts(device.id());
632 for (Link link : counts.keySet()) {
633 addLinkFlows(link, paths, counts.get(link));
634 }
635 }
Simon Huntd2747a02015-04-30 22:41:16 -0700636 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700637 }
638
639 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
Simon Huntda580882015-05-12 20:58:18 -0700640 ObjectNode pathNode = objectNode();
641 ArrayNode linksNode = arrayNode();
642 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700643 boolean noFlows = count == null || count == 0;
644 pathNode.put("class", noFlows ? "secondary" : "primary");
645 pathNode.put("traffic", false);
646 pathNode.set("links", linksNode.add(compactLinkString(link)));
647 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
648 (count == 1 ? " flow" : " flows"))));
649 paths.add(pathNode);
650 }
651
652
653 // Produces JSON message to trigger traffic visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700654 protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
Simon Huntda580882015-05-12 20:58:18 -0700655 ObjectNode payload = objectNode();
656 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700657 payload.set("paths", paths);
658
659 // Classify links based on their traffic traffic first...
660 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
661
662 // Then separate the links into their respective classes and send them out.
663 Map<String, ObjectNode> pathNodes = new HashMap<>();
664 for (BiLink biLink : biLinks.values()) {
665 boolean hasTraffic = biLink.hasTraffic;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700666 String tc = (biLink.classes() + (hasTraffic ? " animated" : "")).trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700667 ObjectNode pathNode = pathNodes.get(tc);
668 if (pathNode == null) {
Simon Huntda580882015-05-12 20:58:18 -0700669 pathNode = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700670 .put("class", tc).put("traffic", hasTraffic);
Simon Huntda580882015-05-12 20:58:18 -0700671 pathNode.set("links", arrayNode());
672 pathNode.set("labels", arrayNode());
Thomas Vachuska329af532015-03-10 02:08:33 -0700673 pathNodes.put(tc, pathNode);
674 paths.add(pathNode);
675 }
676 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
677 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
678 }
679
Simon Huntd2747a02015-04-30 22:41:16 -0700680 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700681 }
682
683 // Classifies the link traffic according to the specified classes.
684 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
685 Map<LinkKey, BiLink> biLinks = new HashMap<>();
686 for (TrafficClass trafficClass : trafficClasses) {
687 for (Intent intent : trafficClass.intents) {
688 boolean isOptical = intent instanceof OpticalConnectivityIntent;
689 List<Intent> installables = intentService.getInstallableIntents(intent.key());
690 if (installables != null) {
691 for (Intent installable : installables) {
692 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
693 if (installable instanceof PathIntent) {
694 classifyLinks(type, biLinks, trafficClass.showTraffic,
695 ((PathIntent) installable).path().links());
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700696 } else if (installable instanceof FlowRuleIntent) {
697 classifyLinks(type, biLinks, trafficClass.showTraffic,
698 linkResources(installable));
Thomas Vachuska329af532015-03-10 02:08:33 -0700699 } else if (installable instanceof LinkCollectionIntent) {
700 classifyLinks(type, biLinks, trafficClass.showTraffic,
701 ((LinkCollectionIntent) installable).links());
702 } else if (installable instanceof OpticalPathIntent) {
703 classifyLinks(type, biLinks, trafficClass.showTraffic,
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700704 ((OpticalPathIntent) installable).path().links());
Thomas Vachuska329af532015-03-10 02:08:33 -0700705 }
706 }
707 }
708 }
709 }
710 return biLinks;
711 }
712
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700713 // Extracts links from the specified flow rule intent resources
714 private Collection<Link> linkResources(Intent installable) {
715 ImmutableList.Builder<Link> builder = ImmutableList.builder();
716 for (NetworkResource r : installable.resources()) {
717 if (r instanceof Link) {
718 builder.add((Link) r);
719 }
720 }
721 return builder.build();
722 }
723
Thomas Vachuska329af532015-03-10 02:08:33 -0700724
725 // Adds the link segments (path or tree) associated with the specified
726 // connectivity intent
727 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
728 boolean showTraffic, Iterable<Link> links) {
729 if (links != null) {
730 for (Link link : links) {
731 BiLink biLink = addLink(biLinks, link);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700732 if (showTraffic) {
Thomas Vachuska25455e72015-06-04 11:31:26 -0700733 biLink.addLoad(getLinkLoad(link));
Thomas Vachuska329af532015-03-10 02:08:33 -0700734 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700735 biLink.addClass(type);
Thomas Vachuska329af532015-03-10 02:08:33 -0700736 }
737 }
738 }
739
740
Thomas Vachuska583bc632015-04-14 10:10:57 -0700741 static BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700742 LinkKey key = canonicalLinkKey(link);
743 BiLink biLink = biLinks.get(key);
744 if (biLink != null) {
745 biLink.setOther(link);
746 } else {
747 biLink = new BiLink(key, link);
748 biLinks.put(key, biLink);
749 }
750 return biLink;
751 }
752
Thomas Vachuska329af532015-03-10 02:08:33 -0700753 // Poor-mans formatting to get the labels with byte counts looking nice.
754 private String formatBytes(long bytes) {
755 String unit;
756 double value;
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700757 if (bytes > GIGA) {
758 value = bytes / GIGA;
759 unit = GBYTES_UNIT;
760 } else if (bytes > MEGA) {
761 value = bytes / MEGA;
762 unit = MBYTES_UNIT;
763 } else if (bytes > KILO) {
764 value = bytes / KILO;
765 unit = KBYTES_UNIT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700766 } else {
767 value = bytes;
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700768 unit = BYTES_UNIT;
769 }
770 DecimalFormat format = new DecimalFormat("#,###.##");
771 return format.format(value) + " " + unit;
772 }
773
774 // Poor-mans formatting to get the labels with byte counts looking nice.
775 private String formatBitRate(long bytes) {
776 String unit;
777 double value;
778 //Convert to bits
779 long bits = bytes * 8;
780 if (bits > GIGA) {
781 value = bits / GIGA;
782 unit = GBITS_UNIT;
783 } else if (bits > MEGA) {
784 value = bits / MEGA;
785 unit = MBITS_UNIT;
786 } else if (bits > KILO) {
787 value = bits / KILO;
788 unit = KBITS_UNIT;
789 } else {
790 value = bits;
791 unit = BITS_UNIT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700792 }
793 DecimalFormat format = new DecimalFormat("#,###.##");
794 return format.format(value) + " " + unit;
795 }
796
797 // Formats the given number into a string.
798 private String format(Number number) {
799 DecimalFormat format = new DecimalFormat("#,###");
800 return format.format(number);
801 }
802
Thomas Vachuska329af532015-03-10 02:08:33 -0700803 // Produces compact string representation of a link.
804 private static String compactLinkString(Link link) {
805 return String.format(COMPACT, link.src().elementId(), link.src().port(),
806 link.dst().elementId(), link.dst().port());
807 }
808
809 // Produces JSON property details.
810 private ObjectNode json(String id, String type, Prop... props) {
Simon Huntda580882015-05-12 20:58:18 -0700811 ObjectNode result = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700812 .put("id", id).put("type", type);
Simon Huntda580882015-05-12 20:58:18 -0700813 ObjectNode pnode = objectNode();
814 ArrayNode porder = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700815 for (Prop p : props) {
816 porder.add(p.key);
817 pnode.put(p.key, p.value);
818 }
819 result.set("propOrder", porder);
820 result.set("props", pnode);
821 return result;
822 }
823
824 // Produces canonical link key, i.e. one that will match link and its inverse.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700825 static LinkKey canonicalLinkKey(Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700826 String sn = link.src().elementId().toString();
827 String dn = link.dst().elementId().toString();
828 return sn.compareTo(dn) < 0 ?
829 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
830 }
831
832 // Representation of link and its inverse and any traffic data.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700833 static class BiLink {
Thomas Vachuska329af532015-03-10 02:08:33 -0700834 public final LinkKey key;
835 public final Link one;
836 public Link two;
837 public boolean hasTraffic = false;
838 public long bytes = 0;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700839
840 private Set<String> classes = new HashSet<>();
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700841 private long rate;
Thomas Vachuska329af532015-03-10 02:08:33 -0700842
843 BiLink(LinkKey key, Link link) {
844 this.key = key;
845 this.one = link;
846 }
847
848 void setOther(Link link) {
849 this.two = link;
850 }
851
852 void addLoad(Load load) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700853 addLoad(load, 0);
854 }
855
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700856 void addLoad(Load load, double threshold) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700857 if (load != null) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700858 this.hasTraffic = hasTraffic || load.rate() > threshold;
Thomas Vachuska329af532015-03-10 02:08:33 -0700859 this.bytes += load.latest();
Thomas Vachuskafdbc4c22015-05-29 15:53:01 -0700860 this.rate += load.rate();
Thomas Vachuska329af532015-03-10 02:08:33 -0700861 }
862 }
863
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700864 void addLoad(Load srcLinkLoad,
865 double srcLinkThreshold,
866 Load dstLinkLoad,
867 double dstLinkThreshold) {
868 //use the max of link load at source or destination
869 if (srcLinkLoad != null) {
870 this.hasTraffic = hasTraffic || srcLinkLoad.rate() > srcLinkThreshold;
871 this.bytes = srcLinkLoad.latest();
872 this.rate = srcLinkLoad.rate();
873 }
874
875 if (dstLinkLoad != null) {
876 if (dstLinkLoad.rate() > this.rate) {
877 this.bytes = dstLinkLoad.latest();
878 this.rate = dstLinkLoad.rate();
879 this.hasTraffic = hasTraffic || dstLinkLoad.rate() > dstLinkThreshold;
880 }
881 }
882 }
883
Thomas Vachuska329af532015-03-10 02:08:33 -0700884 void addClass(String trafficClass) {
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700885 classes.add(trafficClass);
886 }
887
888 String classes() {
889 StringBuilder sb = new StringBuilder();
890 classes.forEach(c -> sb.append(c).append(" "));
891 return sb.toString().trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700892 }
893 }
894
895 // Auxiliary key/value carrier.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700896 static class Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700897 public final String key;
898 public final String value;
899
900 protected Prop(String key, String value) {
901 this.key = key;
902 this.value = value;
903 }
904 }
905
906 // Auxiliary properties separator
Thomas Vachuska583bc632015-04-14 10:10:57 -0700907 static class Separator extends Prop {
Thomas Vachuska329af532015-03-10 02:08:33 -0700908 protected Separator() {
909 super("-", "");
910 }
911 }
912
913 // Auxiliary carrier of data for requesting traffic message.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700914 static class TrafficClass {
Thomas Vachuska329af532015-03-10 02:08:33 -0700915 public final boolean showTraffic;
916 public final String type;
917 public final Iterable<Intent> intents;
918
919 TrafficClass(String type, Iterable<Intent> intents) {
920 this(type, intents, false);
921 }
922
923 TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
924 this.type = type;
925 this.intents = intents;
926 this.showTraffic = showTraffic;
927 }
928 }
929
930}