blob: e6b4ac48a81c04451c6733311a8559dbfbee807f [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;
cheng fan35dc0f22015-06-10 06:02:47 +080030import org.onosproject.incubator.net.tunnel.OpticalTunnelEndPoint;
31import org.onosproject.incubator.net.tunnel.Tunnel;
32import org.onosproject.incubator.net.tunnel.TunnelService;
Thomas Vachuska329af532015-03-10 02:08:33 -070033import org.onosproject.mastership.MastershipService;
34import org.onosproject.net.Annotated;
35import org.onosproject.net.AnnotationKeys;
36import org.onosproject.net.Annotations;
37import org.onosproject.net.ConnectPoint;
38import org.onosproject.net.DefaultEdgeLink;
39import org.onosproject.net.Device;
40import org.onosproject.net.DeviceId;
41import org.onosproject.net.EdgeLink;
42import org.onosproject.net.Host;
43import org.onosproject.net.HostId;
44import org.onosproject.net.HostLocation;
45import org.onosproject.net.Link;
46import org.onosproject.net.LinkKey;
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -070047import org.onosproject.net.NetworkResource;
Thomas Vachuska329af532015-03-10 02:08:33 -070048import org.onosproject.net.PortNumber;
49import org.onosproject.net.device.DeviceEvent;
50import org.onosproject.net.device.DeviceService;
51import org.onosproject.net.flow.FlowEntry;
52import org.onosproject.net.flow.FlowRuleService;
53import org.onosproject.net.flow.TrafficTreatment;
54import org.onosproject.net.flow.instructions.Instruction;
55import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
56import org.onosproject.net.host.HostEvent;
57import org.onosproject.net.host.HostService;
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -070058import org.onosproject.net.intent.FlowRuleIntent;
Thomas Vachuska329af532015-03-10 02:08:33 -070059import org.onosproject.net.intent.Intent;
60import org.onosproject.net.intent.IntentService;
61import org.onosproject.net.intent.LinkCollectionIntent;
62import org.onosproject.net.intent.OpticalConnectivityIntent;
63import org.onosproject.net.intent.OpticalPathIntent;
64import org.onosproject.net.intent.PathIntent;
65import org.onosproject.net.link.LinkEvent;
66import org.onosproject.net.link.LinkService;
67import org.onosproject.net.provider.ProviderId;
68import org.onosproject.net.statistic.Load;
69import org.onosproject.net.statistic.StatisticService;
70import org.onosproject.net.topology.Topology;
71import org.onosproject.net.topology.TopologyService;
Simon Huntd2747a02015-04-30 22:41:16 -070072import org.onosproject.ui.JsonUtils;
Thomas Vachuska329af532015-03-10 02:08:33 -070073import org.onosproject.ui.UiConnection;
Simon Hunta0ddb022015-05-01 09:53:01 -070074import org.onosproject.ui.UiMessageHandler;
Simon Hunt0af1ec32015-07-24 12:17:55 -070075import org.onosproject.ui.topo.PropertyPanel;
Thomas Vachuska329af532015-03-10 02:08:33 -070076import org.slf4j.Logger;
77import org.slf4j.LoggerFactory;
78
79import java.text.DecimalFormat;
80import java.util.ArrayList;
81import java.util.Collection;
82import java.util.Collections;
83import java.util.HashMap;
84import java.util.HashSet;
85import java.util.Iterator;
86import java.util.List;
87import java.util.Map;
88import java.util.Set;
89import java.util.concurrent.ConcurrentHashMap;
90
91import static com.google.common.base.Preconditions.checkNotNull;
92import static com.google.common.base.Strings.isNullOrEmpty;
93import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
94import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_REMOVED;
95import static org.onosproject.cluster.ControllerNode.State.ACTIVE;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -070096import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
Thomas Vachuska329af532015-03-10 02:08:33 -070097import static org.onosproject.net.DeviceId.deviceId;
98import static org.onosproject.net.HostId.hostId;
99import static org.onosproject.net.LinkKey.linkKey;
100import static org.onosproject.net.PortNumber.P0;
101import static org.onosproject.net.PortNumber.portNumber;
102import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
103import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
104import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
105import static org.onosproject.net.host.HostEvent.Type.HOST_REMOVED;
106import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
107import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700108import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.FLOW;
109import static org.onosproject.ui.impl.TopologyViewMessageHandlerBase.StatsType.PORT;
Simon Huntfb940112015-07-29 18:36:35 -0700110import static org.onosproject.ui.topo.TopoConstants.*;
Thomas Vachuska329af532015-03-10 02:08:33 -0700111
112/**
113 * Facility for creating messages bound for the topology viewer.
114 */
Simon Hunta0ddb022015-05-01 09:53:01 -0700115public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
Thomas Vachuska329af532015-03-10 02:08:33 -0700116
Simon Huntd2747a02015-04-30 22:41:16 -0700117 protected static final Logger log =
118 LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700119
Simon Huntd2747a02015-04-30 22:41:16 -0700120 private static final ProviderId PID =
121 new ProviderId("core", "org.onosproject.core", true);
Thomas Vachuska329af532015-03-10 02:08:33 -0700122 private static final String COMPACT = "%s/%s-%s/%s";
123
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700124 private static final double KILO = 1024;
125 private static final double MEGA = 1024 * KILO;
126 private static final double GIGA = 1024 * MEGA;
Thomas Vachuska329af532015-03-10 02:08:33 -0700127
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700128 private static final String GBITS_UNIT = "Gb";
129 private static final String MBITS_UNIT = "Mb";
130 private static final String KBITS_UNIT = "Kb";
131 private static final String BITS_UNIT = "b";
132 private static final String GBYTES_UNIT = "GB";
133 private static final String MBYTES_UNIT = "MB";
134 private static final String KBYTES_UNIT = "KB";
135 private static final String BYTES_UNIT = "B";
136 //4 Kilo Bytes as threshold
137 private static final double BPS_THRESHOLD = 4 * KILO;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700138
Thomas Vachuska329af532015-03-10 02:08:33 -0700139 protected ServiceDirectory directory;
140 protected ClusterService clusterService;
141 protected DeviceService deviceService;
142 protected LinkService linkService;
143 protected HostService hostService;
144 protected MastershipService mastershipService;
145 protected IntentService intentService;
146 protected FlowRuleService flowService;
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700147 protected StatisticService flowStatsService;
148 protected PortStatisticsService portStatsService;
Thomas Vachuska329af532015-03-10 02:08:33 -0700149 protected TopologyService topologyService;
cheng fan35dc0f22015-06-10 06:02:47 +0800150 protected TunnelService tunnelService;
Thomas Vachuska329af532015-03-10 02:08:33 -0700151
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700152 protected enum StatsType {
153 FLOW, PORT
154 }
155
Thomas Vachuska329af532015-03-10 02:08:33 -0700156 private String version;
157
158 // TODO: extract into an external & durable state; good enough for now and demo
159 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
160
161 /**
Thomas Vachuska329af532015-03-10 02:08:33 -0700162 * Returns read-only view of the meta-ui information.
163 *
164 * @return map of id to meta-ui mementos
165 */
166 static Map<String, ObjectNode> getMetaUi() {
167 return Collections.unmodifiableMap(metaUi);
168 }
169
170 @Override
171 public void init(UiConnection connection, ServiceDirectory directory) {
172 super.init(connection, directory);
173 this.directory = checkNotNull(directory, "Directory cannot be null");
174 clusterService = directory.get(ClusterService.class);
175 deviceService = directory.get(DeviceService.class);
176 linkService = directory.get(LinkService.class);
177 hostService = directory.get(HostService.class);
178 mastershipService = directory.get(MastershipService.class);
179 intentService = directory.get(IntentService.class);
180 flowService = directory.get(FlowRuleService.class);
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700181 flowStatsService = directory.get(StatisticService.class);
182 portStatsService = directory.get(PortStatisticsService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700183 topologyService = directory.get(TopologyService.class);
cheng fan35dc0f22015-06-10 06:02:47 +0800184 tunnelService = directory.get(TunnelService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700185
186 String ver = directory.get(CoreService.class).version().toString();
187 version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
188 }
189
Thomas Vachuska329af532015-03-10 02:08:33 -0700190 // Returns the specified set of IP addresses as a string.
191 private String ip(Set<IpAddress> ipAddresses) {
192 Iterator<IpAddress> it = ipAddresses.iterator();
193 return it.hasNext() ? it.next().toString() : "unknown";
194 }
195
196 // Produces JSON structure from annotations.
197 private JsonNode props(Annotations annotations) {
Simon Huntda580882015-05-12 20:58:18 -0700198 ObjectNode props = objectNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700199 if (annotations != null) {
200 for (String key : annotations.keys()) {
201 props.put(key, annotations.value(key));
202 }
203 }
204 return props;
205 }
206
207 // Produces an informational log message event bound to the client.
208 protected ObjectNode info(long id, String message) {
209 return message("info", id, message);
210 }
211
212 // Produces a warning log message event bound to the client.
213 protected ObjectNode warning(long id, String message) {
214 return message("warning", id, message);
215 }
216
217 // Produces an error log message event bound to the client.
218 protected ObjectNode error(long id, String message) {
219 return message("error", id, message);
220 }
221
222 // Produces a log message event bound to the client.
223 private ObjectNode message(String severity, long id, String message) {
Simon Huntda580882015-05-12 20:58:18 -0700224 ObjectNode payload = objectNode()
Simon Huntd2747a02015-04-30 22:41:16 -0700225 .put("severity", severity)
226 .put("message", message);
Thomas Vachuska329af532015-03-10 02:08:33 -0700227
Simon Huntd2747a02015-04-30 22:41:16 -0700228 return JsonUtils.envelope("message", id, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700229 }
230
231 // Produces a set of all hosts listed in the specified JSON array.
232 protected Set<Host> getHosts(ArrayNode array) {
233 Set<Host> hosts = new HashSet<>();
234 if (array != null) {
235 for (JsonNode node : array) {
236 try {
237 addHost(hosts, hostId(node.asText()));
238 } catch (IllegalArgumentException e) {
239 log.debug("Skipping ID {}", node.asText());
240 }
241 }
242 }
243 return hosts;
244 }
245
246 // Adds the specified host to the set of hosts.
247 private void addHost(Set<Host> hosts, HostId hostId) {
248 Host host = hostService.getHost(hostId);
249 if (host != null) {
250 hosts.add(host);
251 }
252 }
253
254
255 // Produces a set of all devices listed in the specified JSON array.
256 protected Set<Device> getDevices(ArrayNode array) {
257 Set<Device> devices = new HashSet<>();
258 if (array != null) {
259 for (JsonNode node : array) {
260 try {
261 addDevice(devices, deviceId(node.asText()));
262 } catch (IllegalArgumentException e) {
263 log.debug("Skipping ID {}", node.asText());
264 }
265 }
266 }
267 return devices;
268 }
269
270 private void addDevice(Set<Device> devices, DeviceId deviceId) {
271 Device device = deviceService.getDevice(deviceId);
272 if (device != null) {
273 devices.add(device);
274 }
275 }
276
277 protected void addHover(Set<Host> hosts, Set<Device> devices, String hover) {
278 try {
279 addHost(hosts, hostId(hover));
280 } catch (IllegalArgumentException e) {
281 try {
282 addDevice(devices, deviceId(hover));
283 } catch (IllegalArgumentException ne) {
284 log.debug("Skipping ID {}", hover);
285 }
286 }
287 }
288
289 // Produces a cluster instance message to the client.
290 protected ObjectNode instanceMessage(ClusterEvent event, String messageType) {
291 ControllerNode node = event.subject();
292 int switchCount = mastershipService.getDevicesOf(node.id()).size();
Simon Huntda580882015-05-12 20:58:18 -0700293 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700294 .put("id", node.id().toString())
295 .put("ip", node.ip().toString())
296 .put("online", clusterService.getState(node.id()) == ACTIVE)
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700297 .put("uiAttached", node.equals(clusterService.getLocalNode()))
Thomas Vachuska329af532015-03-10 02:08:33 -0700298 .put("switches", switchCount);
299
Simon Huntda580882015-05-12 20:58:18 -0700300 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700301 labels.add(node.id().toString());
302 labels.add(node.ip().toString());
303
304 // Add labels, props and stuff the payload into envelope.
305 payload.set("labels", labels);
306 addMetaUi(node.id().toString(), payload);
307
308 String type = messageType != null ? messageType :
309 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
310 ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
311 "addInstance")));
Simon Huntd2747a02015-04-30 22:41:16 -0700312 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700313 }
314
315 // Produces a device event message to the client.
316 protected ObjectNode deviceMessage(DeviceEvent event) {
317 Device device = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700318 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700319 .put("id", device.id().toString())
320 .put("type", device.type().toString().toLowerCase())
321 .put("online", deviceService.isAvailable(device.id()))
322 .put("master", master(device.id()));
323
324 // Generate labels: id, chassis id, no-label, optional-name
325 String name = device.annotations().value(AnnotationKeys.NAME);
Simon Huntda580882015-05-12 20:58:18 -0700326 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700327 labels.add("");
328 labels.add(isNullOrEmpty(name) ? device.id().toString() : name);
329 labels.add(device.id().toString());
330
331 // Add labels, props and stuff the payload into envelope.
332 payload.set("labels", labels);
333 payload.set("props", props(device.annotations()));
334 addGeoLocation(device, payload);
335 addMetaUi(device.id().toString(), payload);
336
337 String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
338 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
Simon Huntd2747a02015-04-30 22:41:16 -0700339 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700340 }
341
342 // Produces a link event message to the client.
343 protected ObjectNode linkMessage(LinkEvent event) {
344 Link link = event.subject();
Simon Huntda580882015-05-12 20:58:18 -0700345 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700346 .put("id", compactLinkString(link))
347 .put("type", link.type().toString().toLowerCase())
348 .put("online", link.state() == Link.State.ACTIVE)
349 .put("linkWidth", 1.2)
350 .put("src", link.src().deviceId().toString())
351 .put("srcPort", link.src().port().toString())
352 .put("dst", link.dst().deviceId().toString())
353 .put("dstPort", link.dst().port().toString());
354 String type = (event.type() == LINK_ADDED) ? "addLink" :
355 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
Simon Huntd2747a02015-04-30 22:41:16 -0700356 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700357 }
358
359 // Produces a host event message to the client.
360 protected ObjectNode hostMessage(HostEvent event) {
361 Host host = event.subject();
362 String hostType = host.annotations().value(AnnotationKeys.TYPE);
Simon Huntda580882015-05-12 20:58:18 -0700363 ObjectNode payload = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700364 .put("id", host.id().toString())
365 .put("type", isNullOrEmpty(hostType) ? "endstation" : hostType)
366 .put("ingress", compactLinkString(edgeLink(host, true)))
367 .put("egress", compactLinkString(edgeLink(host, false)));
Simon Huntda580882015-05-12 20:58:18 -0700368 payload.set("cp", hostConnect(host.location()));
369 payload.set("labels", labels(ip(host.ipAddresses()),
Thomas Vachuska329af532015-03-10 02:08:33 -0700370 host.mac().toString()));
371 payload.set("props", props(host.annotations()));
372 addGeoLocation(host, payload);
373 addMetaUi(host.id().toString(), payload);
374
375 String type = (event.type() == HOST_ADDED) ? "addHost" :
376 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
Simon Huntd2747a02015-04-30 22:41:16 -0700377 return JsonUtils.envelope(type, 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700378 }
379
380 // Encodes the specified host location into a JSON object.
Simon Huntda580882015-05-12 20:58:18 -0700381 private ObjectNode hostConnect(HostLocation location) {
382 return objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700383 .put("device", location.deviceId().toString())
384 .put("port", location.port().toLong());
385 }
386
387 // Encodes the specified list of labels a JSON array.
Simon Huntda580882015-05-12 20:58:18 -0700388 private ArrayNode labels(String... labels) {
389 ArrayNode json = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700390 for (String label : labels) {
391 json.add(label);
392 }
393 return json;
394 }
395
396 // Returns the name of the master node for the specified device id.
397 private String master(DeviceId deviceId) {
398 NodeId master = mastershipService.getMasterFor(deviceId);
399 return master != null ? master.toString() : "";
400 }
401
402 // Generates an edge link from the specified host location.
403 private EdgeLink edgeLink(Host host, boolean ingress) {
404 return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
405 host.location(), ingress);
406 }
407
408 // Adds meta UI information for the specified object.
409 private void addMetaUi(String id, ObjectNode payload) {
410 ObjectNode meta = metaUi.get(id);
411 if (meta != null) {
412 payload.set("metaUi", meta);
413 }
414 }
415
416 // Adds a geo location JSON to the specified payload object.
417 private void addGeoLocation(Annotated annotated, ObjectNode payload) {
418 Annotations annotations = annotated.annotations();
419 if (annotations == null) {
420 return;
421 }
422
423 String slat = annotations.value(AnnotationKeys.LATITUDE);
424 String slng = annotations.value(AnnotationKeys.LONGITUDE);
425 try {
426 if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
427 double lat = Double.parseDouble(slat);
428 double lng = Double.parseDouble(slng);
Simon Huntda580882015-05-12 20:58:18 -0700429 ObjectNode loc = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700430 .put("type", "latlng").put("lat", lat).put("lng", lng);
431 payload.set("location", loc);
432 }
433 } catch (NumberFormatException e) {
434 log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
435 }
436 }
437
438 // Updates meta UI information for the specified object.
Simon Huntd2747a02015-04-30 22:41:16 -0700439 protected void updateMetaUi(ObjectNode payload) {
440 metaUi.put(JsonUtils.string(payload, "id"),
441 JsonUtils.node(payload, "memento"));
Thomas Vachuska329af532015-03-10 02:08:33 -0700442 }
443
Simon Huntb745ca62015-07-28 15:37:11 -0700444 // -----------------------------------------------------------------------
445 // Create models of the data to return, that overlays can adjust / augment
446
447 // Returns property panel model for summary response.
Simon Hunt0af1ec32015-07-24 12:17:55 -0700448 protected PropertyPanel summmaryMessage(long sid) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700449 Topology topology = topologyService.currentTopology();
Simon Hunt0af1ec32015-07-24 12:17:55 -0700450
Simon Hunt00a27ff2015-07-28 18:53:40 -0700451 return new PropertyPanel("ONOS Summary", "node")
Simon Huntfb940112015-07-29 18:36:35 -0700452 .addProp(Properties.DEVICES, topology.deviceCount())
453 .addProp(Properties.LINKS, topology.linkCount())
454 .addProp(Properties.HOSTS, hostService.getHostCount())
455 .addProp(Properties.TOPOLOGY_SSCS, topology.clusterCount())
Simon Hunt00a27ff2015-07-28 18:53:40 -0700456 .addSeparator()
Simon Huntfb940112015-07-29 18:36:35 -0700457 .addProp(Properties.INTENTS, intentService.getIntentCount())
458 .addProp(Properties.TUNNELS, tunnelService.tunnelCount())
459 .addProp(Properties.FLOWS, flowService.getFlowRuleCount())
460 .addProp(Properties.VERSION, version);
Thomas Vachuska329af532015-03-10 02:08:33 -0700461 }
462
Simon Huntb745ca62015-07-28 15:37:11 -0700463 // Returns property panel model for device details response.
464 protected PropertyPanel deviceDetails(DeviceId deviceId, long sid) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700465 Device device = deviceService.getDevice(deviceId);
466 Annotations annot = device.annotations();
467 String name = annot.value(AnnotationKeys.NAME);
468 int portCount = deviceService.getPorts(deviceId).size();
469 int flowCount = getFlowCount(deviceId);
cheng fan35dc0f22015-06-10 06:02:47 +0800470 int tunnelCount = getTunnelCount(deviceId);
Simon Huntb745ca62015-07-28 15:37:11 -0700471
472 String title = isNullOrEmpty(name) ? deviceId.toString() : name;
473 String typeId = device.type().toString().toLowerCase();
474
475 PropertyPanel pp = new PropertyPanel(title, typeId)
Simon Huntfb940112015-07-29 18:36:35 -0700476 .id(deviceId.toString())
477 .addProp(Properties.URI, deviceId.toString())
478 .addProp(Properties.VENDOR, device.manufacturer())
479 .addProp(Properties.HW_VERSION, device.hwVersion())
480 .addProp(Properties.SW_VERSION, device.swVersion())
481 .addProp(Properties.SERIAL_NUMBER, device.serialNumber())
482 .addProp(Properties.PROTOCOL, annot.value(AnnotationKeys.PROTOCOL))
483 .addSeparator()
484 .addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
485 .addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE))
486 .addSeparator()
487 .addProp(Properties.PORTS, portCount)
488 .addProp(Properties.FLOWS, flowCount)
489 .addProp(Properties.TUNNELS, tunnelCount);
Simon Hunt00a27ff2015-07-28 18:53:40 -0700490
491 // TODO: add button descriptors
Simon Huntb745ca62015-07-28 15:37:11 -0700492
493 return pp;
Thomas Vachuska329af532015-03-10 02:08:33 -0700494 }
495
496 protected int getFlowCount(DeviceId deviceId) {
497 int count = 0;
Simon Huntb745ca62015-07-28 15:37:11 -0700498 for (FlowEntry flowEntry : flowService.getFlowEntries(deviceId)) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700499 count++;
Thomas Vachuska329af532015-03-10 02:08:33 -0700500 }
501 return count;
502 }
503
cheng fan35dc0f22015-06-10 06:02:47 +0800504 protected int getTunnelCount(DeviceId deviceId) {
505 int count = 0;
506 Collection<Tunnel> tunnels = tunnelService.queryAllTunnels();
507 for (Tunnel tunnel : tunnels) {
508 OpticalTunnelEndPoint src = (OpticalTunnelEndPoint) tunnel.src();
509 OpticalTunnelEndPoint dst = (OpticalTunnelEndPoint) tunnel.dst();
510 DeviceId srcDevice = (DeviceId) src.elementId().get();
511 DeviceId dstDevice = (DeviceId) dst.elementId().get();
Simon Huntb745ca62015-07-28 15:37:11 -0700512 if (srcDevice.toString().equals(deviceId.toString()) ||
513 dstDevice.toString().equals(deviceId.toString())) {
cheng fan35dc0f22015-06-10 06:02:47 +0800514 count++;
515 }
516 }
517 return count;
518 }
519
Thomas Vachuska329af532015-03-10 02:08:33 -0700520 // Counts all entries that egress on the given device links.
521 protected Map<Link, Integer> getFlowCounts(DeviceId deviceId) {
522 List<FlowEntry> entries = new ArrayList<>();
523 Set<Link> links = new HashSet<>(linkService.getDeviceEgressLinks(deviceId));
524 Set<Host> hosts = hostService.getConnectedHosts(deviceId);
Simon Huntb745ca62015-07-28 15:37:11 -0700525 for (FlowEntry flowEntry : flowService.getFlowEntries(deviceId)) {
526 entries.add(flowEntry);
Thomas Vachuska329af532015-03-10 02:08:33 -0700527 }
528
529 // Add all edge links to the set
530 if (hosts != null) {
531 for (Host host : hosts) {
532 links.add(new DefaultEdgeLink(host.providerId(),
533 new ConnectPoint(host.id(), P0),
534 host.location(), false));
535 }
536 }
537
538 Map<Link, Integer> counts = new HashMap<>();
539 for (Link link : links) {
540 counts.put(link, getEgressFlows(link, entries));
541 }
542 return counts;
543 }
544
545 // Counts all entries that egress on the link source port.
546 private Integer getEgressFlows(Link link, List<FlowEntry> entries) {
547 int count = 0;
548 PortNumber out = link.src().port();
549 for (FlowEntry entry : entries) {
550 TrafficTreatment treatment = entry.treatment();
Ray Milkey42507352015-03-20 15:16:10 -0700551 for (Instruction instruction : treatment.allInstructions()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700552 if (instruction.type() == Instruction.Type.OUTPUT &&
553 ((OutputInstruction) instruction).port().equals(out)) {
554 count++;
555 }
556 }
557 }
558 return count;
559 }
560
561
562 // Returns host details response.
Simon Huntb745ca62015-07-28 15:37:11 -0700563 protected PropertyPanel hostDetails(HostId hostId, long sid) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700564 Host host = hostService.getHost(hostId);
565 Annotations annot = host.annotations();
566 String type = annot.value(AnnotationKeys.TYPE);
567 String name = annot.value(AnnotationKeys.NAME);
568 String vlan = host.vlan().toString();
Simon Huntb745ca62015-07-28 15:37:11 -0700569
570 String title = isNullOrEmpty(name) ? hostId.toString() : name;
571 String typeId = isNullOrEmpty(type) ? "endstation" : type;
572
573 PropertyPanel pp = new PropertyPanel(title, typeId)
Simon Huntfb940112015-07-29 18:36:35 -0700574 .id(hostId.toString())
575 .addProp(Properties.MAC, host.mac())
576 .addProp(Properties.IP, host.ipAddresses(), "[\\[\\]]")
577 .addProp(Properties.VLAN, vlan.equals("-1") ? "none" : vlan)
578 .addSeparator()
579 .addProp(Properties.LATITUDE, annot.value(AnnotationKeys.LATITUDE))
580 .addProp(Properties.LONGITUDE, annot.value(AnnotationKeys.LONGITUDE));
Simon Huntb745ca62015-07-28 15:37:11 -0700581
Simon Hunt00a27ff2015-07-28 18:53:40 -0700582 // TODO: add button descriptors
Simon Huntb745ca62015-07-28 15:37:11 -0700583 return pp;
Thomas Vachuska329af532015-03-10 02:08:33 -0700584 }
585
586
Simon Huntb745ca62015-07-28 15:37:11 -0700587 // TODO: migrate to Traffic overlay
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700588 // Produces JSON message to trigger flow traffic overview visualization
589 protected ObjectNode trafficSummaryMessage(StatsType type) {
Simon Huntda580882015-05-12 20:58:18 -0700590 ObjectNode payload = objectNode();
591 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700592 payload.set("paths", paths);
593
Simon Huntda580882015-05-12 20:58:18 -0700594 ObjectNode pathNodeN = objectNode();
595 ArrayNode linksNodeN = arrayNode();
596 ArrayNode labelsN = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700597
598 pathNodeN.put("class", "plain").put("traffic", false);
599 pathNodeN.set("links", linksNodeN);
600 pathNodeN.set("labels", labelsN);
601 paths.add(pathNodeN);
602
Simon Huntda580882015-05-12 20:58:18 -0700603 ObjectNode pathNodeT = objectNode();
604 ArrayNode linksNodeT = arrayNode();
605 ArrayNode labelsT = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700606
607 pathNodeT.put("class", "secondary").put("traffic", true);
608 pathNodeT.set("links", linksNodeT);
609 pathNodeT.set("labels", labelsT);
610 paths.add(pathNodeT);
611
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700612 Map<LinkKey, BiLink> biLinks = consolidateLinks(linkService.getLinks());
613 addEdgeLinks(biLinks);
614 for (BiLink link : biLinks.values()) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700615 boolean bi = link.two != null;
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700616 if (type == FLOW) {
617 link.addLoad(getLinkLoad(link.one));
618 link.addLoad(bi ? getLinkLoad(link.two) : null);
619 } else if (type == PORT) {
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700620 //For a bi-directional traffic links, use
621 //the max link rate of either direction
622 link.addLoad(portStatsService.load(link.one.src()),
623 BPS_THRESHOLD,
624 portStatsService.load(link.one.dst()),
625 BPS_THRESHOLD);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700626 }
627 if (link.hasTraffic) {
628 linksNodeT.add(compactLinkString(link.one));
629 labelsT.add(type == PORT ?
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700630 formatBitRate(link.rate) + "ps" :
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700631 formatBytes(link.bytes));
632 } else {
633 linksNodeN.add(compactLinkString(link.one));
634 labelsN.add("");
Thomas Vachuska329af532015-03-10 02:08:33 -0700635 }
636 }
Simon Huntd2747a02015-04-30 22:41:16 -0700637 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700638 }
639
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700640 private Load getLinkLoad(Link link) {
641 if (link.src().elementId() instanceof DeviceId) {
642 return flowStatsService.load(link);
643 }
644 return null;
645 }
646
647 private void addEdgeLinks(Map<LinkKey, BiLink> biLinks) {
648 hostService.getHosts().forEach(host -> {
Thomas Vachuska25455e72015-06-04 11:31:26 -0700649 addLink(biLinks, createEdgeLink(host, true));
650 addLink(biLinks, createEdgeLink(host, false));
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700651 });
652 }
653
654 private Map<LinkKey, BiLink> consolidateLinks(Iterable<Link> links) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700655 Map<LinkKey, BiLink> biLinks = new HashMap<>();
656 for (Link link : links) {
657 addLink(biLinks, link);
658 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700659 return biLinks;
Thomas Vachuska329af532015-03-10 02:08:33 -0700660 }
661
662 // Produces JSON message to trigger flow overview visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700663 protected ObjectNode flowSummaryMessage(Set<Device> devices) {
Simon Huntda580882015-05-12 20:58:18 -0700664 ObjectNode payload = objectNode();
665 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700666 payload.set("paths", paths);
667
668 for (Device device : devices) {
669 Map<Link, Integer> counts = getFlowCounts(device.id());
670 for (Link link : counts.keySet()) {
671 addLinkFlows(link, paths, counts.get(link));
672 }
673 }
Simon Huntd2747a02015-04-30 22:41:16 -0700674 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700675 }
676
677 private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
Simon Huntda580882015-05-12 20:58:18 -0700678 ObjectNode pathNode = objectNode();
679 ArrayNode linksNode = arrayNode();
680 ArrayNode labels = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700681 boolean noFlows = count == null || count == 0;
682 pathNode.put("class", noFlows ? "secondary" : "primary");
683 pathNode.put("traffic", false);
684 pathNode.set("links", linksNode.add(compactLinkString(link)));
685 pathNode.set("labels", labels.add(noFlows ? "" : (count.toString() +
686 (count == 1 ? " flow" : " flows"))));
687 paths.add(pathNode);
688 }
689
690
691 // Produces JSON message to trigger traffic visualization
Simon Huntd2747a02015-04-30 22:41:16 -0700692 protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
Simon Huntda580882015-05-12 20:58:18 -0700693 ObjectNode payload = objectNode();
694 ArrayNode paths = arrayNode();
Thomas Vachuska329af532015-03-10 02:08:33 -0700695 payload.set("paths", paths);
696
697 // Classify links based on their traffic traffic first...
698 Map<LinkKey, BiLink> biLinks = classifyLinkTraffic(trafficClasses);
699
700 // Then separate the links into their respective classes and send them out.
701 Map<String, ObjectNode> pathNodes = new HashMap<>();
702 for (BiLink biLink : biLinks.values()) {
703 boolean hasTraffic = biLink.hasTraffic;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700704 String tc = (biLink.classes() + (hasTraffic ? " animated" : "")).trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700705 ObjectNode pathNode = pathNodes.get(tc);
706 if (pathNode == null) {
Simon Huntda580882015-05-12 20:58:18 -0700707 pathNode = objectNode()
Thomas Vachuska329af532015-03-10 02:08:33 -0700708 .put("class", tc).put("traffic", hasTraffic);
Simon Huntda580882015-05-12 20:58:18 -0700709 pathNode.set("links", arrayNode());
710 pathNode.set("labels", arrayNode());
Thomas Vachuska329af532015-03-10 02:08:33 -0700711 pathNodes.put(tc, pathNode);
712 paths.add(pathNode);
713 }
714 ((ArrayNode) pathNode.path("links")).add(compactLinkString(biLink.one));
715 ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
716 }
717
Simon Huntd2747a02015-04-30 22:41:16 -0700718 return JsonUtils.envelope("showTraffic", 0, payload);
Thomas Vachuska329af532015-03-10 02:08:33 -0700719 }
720
721 // Classifies the link traffic according to the specified classes.
722 private Map<LinkKey, BiLink> classifyLinkTraffic(TrafficClass... trafficClasses) {
723 Map<LinkKey, BiLink> biLinks = new HashMap<>();
724 for (TrafficClass trafficClass : trafficClasses) {
725 for (Intent intent : trafficClass.intents) {
726 boolean isOptical = intent instanceof OpticalConnectivityIntent;
727 List<Intent> installables = intentService.getInstallableIntents(intent.key());
728 if (installables != null) {
729 for (Intent installable : installables) {
730 String type = isOptical ? trafficClass.type + " optical" : trafficClass.type;
731 if (installable instanceof PathIntent) {
732 classifyLinks(type, biLinks, trafficClass.showTraffic,
733 ((PathIntent) installable).path().links());
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700734 } else if (installable instanceof FlowRuleIntent) {
735 classifyLinks(type, biLinks, trafficClass.showTraffic,
736 linkResources(installable));
Thomas Vachuska329af532015-03-10 02:08:33 -0700737 } else if (installable instanceof LinkCollectionIntent) {
738 classifyLinks(type, biLinks, trafficClass.showTraffic,
739 ((LinkCollectionIntent) installable).links());
740 } else if (installable instanceof OpticalPathIntent) {
741 classifyLinks(type, biLinks, trafficClass.showTraffic,
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700742 ((OpticalPathIntent) installable).path().links());
Thomas Vachuska329af532015-03-10 02:08:33 -0700743 }
744 }
745 }
746 }
747 }
748 return biLinks;
749 }
750
Thomas Vachuskac0fe09a2015-05-21 12:56:22 -0700751 // Extracts links from the specified flow rule intent resources
752 private Collection<Link> linkResources(Intent installable) {
753 ImmutableList.Builder<Link> builder = ImmutableList.builder();
754 for (NetworkResource r : installable.resources()) {
755 if (r instanceof Link) {
756 builder.add((Link) r);
757 }
758 }
759 return builder.build();
760 }
761
Thomas Vachuska329af532015-03-10 02:08:33 -0700762
763 // Adds the link segments (path or tree) associated with the specified
764 // connectivity intent
765 private void classifyLinks(String type, Map<LinkKey, BiLink> biLinks,
766 boolean showTraffic, Iterable<Link> links) {
767 if (links != null) {
768 for (Link link : links) {
769 BiLink biLink = addLink(biLinks, link);
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700770 if (showTraffic) {
Thomas Vachuska25455e72015-06-04 11:31:26 -0700771 biLink.addLoad(getLinkLoad(link));
Thomas Vachuska329af532015-03-10 02:08:33 -0700772 }
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700773 biLink.addClass(type);
Thomas Vachuska329af532015-03-10 02:08:33 -0700774 }
775 }
776 }
777
778
Thomas Vachuska583bc632015-04-14 10:10:57 -0700779 static BiLink addLink(Map<LinkKey, BiLink> biLinks, Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700780 LinkKey key = canonicalLinkKey(link);
781 BiLink biLink = biLinks.get(key);
782 if (biLink != null) {
783 biLink.setOther(link);
784 } else {
785 biLink = new BiLink(key, link);
786 biLinks.put(key, biLink);
787 }
788 return biLink;
789 }
790
Thomas Vachuska329af532015-03-10 02:08:33 -0700791 // Poor-mans formatting to get the labels with byte counts looking nice.
792 private String formatBytes(long bytes) {
793 String unit;
794 double value;
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700795 if (bytes > GIGA) {
796 value = bytes / GIGA;
797 unit = GBYTES_UNIT;
798 } else if (bytes > MEGA) {
799 value = bytes / MEGA;
800 unit = MBYTES_UNIT;
801 } else if (bytes > KILO) {
802 value = bytes / KILO;
803 unit = KBYTES_UNIT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700804 } else {
805 value = bytes;
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700806 unit = BYTES_UNIT;
807 }
808 DecimalFormat format = new DecimalFormat("#,###.##");
809 return format.format(value) + " " + unit;
810 }
811
Simon Hunt80170a72015-06-10 19:49:57 -0700812 // Poor-mans formatting to get the labels with bit rate looking nice.
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700813 private String formatBitRate(long bytes) {
814 String unit;
815 double value;
816 //Convert to bits
817 long bits = bytes * 8;
818 if (bits > GIGA) {
819 value = bits / GIGA;
820 unit = GBITS_UNIT;
Simon Hunt80170a72015-06-10 19:49:57 -0700821
822 // NOTE: temporary hack to clip rate at 10.0 Gbps
823 // Added for the CORD Fabric demo at ONS 2015
824 if (value > 10.0) {
825 value = 10.0;
826 }
827
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700828 } else if (bits > MEGA) {
829 value = bits / MEGA;
830 unit = MBITS_UNIT;
831 } else if (bits > KILO) {
832 value = bits / KILO;
833 unit = KBITS_UNIT;
834 } else {
835 value = bits;
836 unit = BITS_UNIT;
Thomas Vachuska329af532015-03-10 02:08:33 -0700837 }
838 DecimalFormat format = new DecimalFormat("#,###.##");
839 return format.format(value) + " " + unit;
840 }
841
Thomas Vachuska329af532015-03-10 02:08:33 -0700842 // Produces compact string representation of a link.
843 private static String compactLinkString(Link link) {
844 return String.format(COMPACT, link.src().elementId(), link.src().port(),
845 link.dst().elementId(), link.dst().port());
846 }
847
Simon Huntb745ca62015-07-28 15:37:11 -0700848 // translates the property panel into JSON, for returning to the client
Simon Hunt0af1ec32015-07-24 12:17:55 -0700849 protected ObjectNode json(PropertyPanel pp) {
850 ObjectNode result = objectNode()
Simon Huntb745ca62015-07-28 15:37:11 -0700851 .put("title", pp.title())
852 .put("type", pp.typeId())
853 .put("id", pp.id());
854
Simon Hunt0af1ec32015-07-24 12:17:55 -0700855 ObjectNode pnode = objectNode();
856 ArrayNode porder = arrayNode();
857 for (PropertyPanel.Prop p : pp.properties()) {
858 porder.add(p.key());
859 pnode.put(p.key(), p.value());
860 }
861 result.set("propOrder", porder);
862 result.set("props", pnode);
Simon Huntfb940112015-07-29 18:36:35 -0700863
864 ArrayNode buttons = arrayNode();
865 for (PropertyPanel.Button b : pp.buttons()) {
866 buttons.add(b.id());
867 }
868 result.set("buttons", buttons);
Simon Hunt0af1ec32015-07-24 12:17:55 -0700869 return result;
870 }
871
Thomas Vachuska329af532015-03-10 02:08:33 -0700872 // Produces canonical link key, i.e. one that will match link and its inverse.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700873 static LinkKey canonicalLinkKey(Link link) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700874 String sn = link.src().elementId().toString();
875 String dn = link.dst().elementId().toString();
876 return sn.compareTo(dn) < 0 ?
877 linkKey(link.src(), link.dst()) : linkKey(link.dst(), link.src());
878 }
879
880 // Representation of link and its inverse and any traffic data.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700881 static class BiLink {
Thomas Vachuska329af532015-03-10 02:08:33 -0700882 public final LinkKey key;
883 public final Link one;
884 public Link two;
885 public boolean hasTraffic = false;
886 public long bytes = 0;
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700887
888 private Set<String> classes = new HashSet<>();
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700889 private long rate;
Thomas Vachuska329af532015-03-10 02:08:33 -0700890
891 BiLink(LinkKey key, Link link) {
892 this.key = key;
893 this.one = link;
894 }
895
896 void setOther(Link link) {
897 this.two = link;
898 }
899
900 void addLoad(Load load) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700901 addLoad(load, 0);
902 }
903
Thomas Vachuska204cb6c2015-06-04 00:03:06 -0700904 void addLoad(Load load, double threshold) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700905 if (load != null) {
Thomas Vachuskaf0397b52015-05-29 13:50:17 -0700906 this.hasTraffic = hasTraffic || load.rate() > threshold;
Thomas Vachuska329af532015-03-10 02:08:33 -0700907 this.bytes += load.latest();
Thomas Vachuskafdbc4c22015-05-29 15:53:01 -0700908 this.rate += load.rate();
Thomas Vachuska329af532015-03-10 02:08:33 -0700909 }
910 }
911
Srikanth Vavilapalli8c1ccca2015-06-08 19:23:24 -0700912 void addLoad(Load srcLinkLoad,
913 double srcLinkThreshold,
914 Load dstLinkLoad,
915 double dstLinkThreshold) {
916 //use the max of link load at source or destination
917 if (srcLinkLoad != null) {
918 this.hasTraffic = hasTraffic || srcLinkLoad.rate() > srcLinkThreshold;
919 this.bytes = srcLinkLoad.latest();
920 this.rate = srcLinkLoad.rate();
921 }
922
923 if (dstLinkLoad != null) {
924 if (dstLinkLoad.rate() > this.rate) {
925 this.bytes = dstLinkLoad.latest();
926 this.rate = dstLinkLoad.rate();
927 this.hasTraffic = hasTraffic || dstLinkLoad.rate() > dstLinkThreshold;
928 }
929 }
930 }
931
Thomas Vachuska329af532015-03-10 02:08:33 -0700932 void addClass(String trafficClass) {
Thomas Vachuska5d5858b2015-03-23 16:31:10 -0700933 classes.add(trafficClass);
934 }
935
936 String classes() {
937 StringBuilder sb = new StringBuilder();
938 classes.forEach(c -> sb.append(c).append(" "));
939 return sb.toString().trim();
Thomas Vachuska329af532015-03-10 02:08:33 -0700940 }
941 }
942
Thomas Vachuska329af532015-03-10 02:08:33 -0700943
Simon Hunt93735af2015-07-28 12:10:30 -0700944 // TODO: move this to traffic overlay component
Thomas Vachuska329af532015-03-10 02:08:33 -0700945 // Auxiliary carrier of data for requesting traffic message.
Thomas Vachuska583bc632015-04-14 10:10:57 -0700946 static class TrafficClass {
Thomas Vachuska329af532015-03-10 02:08:33 -0700947 public final boolean showTraffic;
948 public final String type;
949 public final Iterable<Intent> intents;
950
951 TrafficClass(String type, Iterable<Intent> intents) {
952 this(type, intents, false);
953 }
954
955 TrafficClass(String type, Iterable<Intent> intents, boolean showTraffic) {
956 this.type = type;
957 this.intents = intents;
958 this.showTraffic = showTraffic;
959 }
960 }
961
962}